Migrate to azure-storage-blob-go (#9577)
The azure sdk for go is maintenance-only for storage, see https://github.com/Azure/azure-sdk-for-go/tree/master/storage\#azure-storage-sdk-for-go-preview Migrate to new azure-storage-blob-go SDK Minor test improvements Fix #9661
This commit is contained in:
parent
2d244f6817
commit
9b599c8162
3
go.mod
3
go.mod
|
@ -10,7 +10,7 @@ require (
|
|||
cloud.google.com/go v0.56.0
|
||||
cloud.google.com/go/spanner v1.5.1
|
||||
cloud.google.com/go/storage v1.6.0
|
||||
github.com/Azure/azure-sdk-for-go v36.2.0+incompatible
|
||||
github.com/Azure/azure-storage-blob-go v0.10.0
|
||||
github.com/Azure/go-autorest/autorest v0.10.1
|
||||
github.com/DataDog/zstd v1.4.5 // indirect
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
|
@ -32,7 +32,6 @@ require (
|
|||
github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c
|
||||
github.com/coreos/go-semver v0.2.0
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc
|
||||
github.com/dnaeon/go-vcr v1.0.1 // indirect
|
||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/dsnet/compress v0.0.1 // indirect
|
||||
|
|
11
go.sum
11
go.sum
|
@ -37,8 +37,12 @@ code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f h1:UrKzEwTg
|
|||
code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f/go.mod h1:sk5LnIjB/nIEU7yP5sDQExVm62wu0pBh3yrElngUisI=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/Azure/azure-pipeline-go v0.2.2 h1:6oiIS9yaG6XCCzhgAgKFfIWyo4LLCiDhZot6ltoThhY=
|
||||
github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc=
|
||||
github.com/Azure/azure-sdk-for-go v36.2.0+incompatible h1:09cv2WoH0g6jl6m2iT+R9qcIPZKhXEL0sbmLhxP895s=
|
||||
github.com/Azure/azure-sdk-for-go v36.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-storage-blob-go v0.10.0 h1:evCwGreYo3XLeBV4vSxLbLiYb6e0SzsJiXQVRGsRXxs=
|
||||
github.com/Azure/azure-storage-blob-go v0.10.0/go.mod h1:ep1edmW+kNQx4UfWM9heESNmQdijykocJ0YOxmMX8SE=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
|
||||
|
@ -51,8 +55,9 @@ github.com/Azure/go-autorest/autorest/adal v0.6.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S
|
|||
github.com/Azure/go-autorest/autorest/adal v0.7.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.8.2 h1:O1X4oexUxnZCaEUGsvMnr8ZGj8HI37tNezwY4npRqA0=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.8.3 h1:O1AGG9Xig71FxdX9HO5pGNyZ7TbSyHaVg+5eJO/jSGw=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.8.3/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.4.0/go.mod h1:Oo5cRhLvZteXzI2itUm5ziqsoIxRkzrt3t61FeZaS18=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.4.2 h1:iM6UAvjR97ZIeR93qTcwpKNMpV+/FTWjwEbuPD495Tk=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM=
|
||||
|
@ -232,8 +237,6 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
|
|||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4=
|
||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY=
|
||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v1.4.2-0.20200319182547-c7ad2b866182/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
|
@ -647,6 +650,8 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO
|
|||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d h1:oNAwILwmgWKFpuU+dXvI6dl9jG2mAWAZLX3r9s0PPiw=
|
||||
github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
|
|
|
@ -2,20 +2,20 @@ package azure
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
storage "github.com/Azure/azure-sdk-for-go/storage"
|
||||
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
metrics "github.com/armon/go-metrics"
|
||||
"github.com/hashicorp/errwrap"
|
||||
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/sdk/helper/strutil"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
|
@ -31,7 +31,7 @@ const (
|
|||
// AzureBackend is a physical backend that stores data
|
||||
// within an Azure blob container.
|
||||
type AzureBackend struct {
|
||||
container *storage.Container
|
||||
container *azblob.ContainerURL
|
||||
logger log.Logger
|
||||
permitPool *physical.PermitPool
|
||||
}
|
||||
|
@ -75,19 +75,19 @@ func NewAzureBackend(conf map[string]string, logger log.Logger) (physical.Backen
|
|||
}
|
||||
}
|
||||
|
||||
environmentUrl := os.Getenv("AZURE_ARM_ENDPOINT")
|
||||
if environmentUrl == "" {
|
||||
environmentUrl = conf["arm_endpoint"]
|
||||
environmentURL := os.Getenv("AZURE_ARM_ENDPOINT")
|
||||
if environmentURL == "" {
|
||||
environmentURL = conf["arm_endpoint"]
|
||||
}
|
||||
|
||||
var environment azure.Environment
|
||||
var err error
|
||||
|
||||
if environmentUrl != "" {
|
||||
environment, err = azure.EnvironmentFromURL(environmentUrl)
|
||||
if environmentURL != "" {
|
||||
environment, err = azure.EnvironmentFromURL(environmentURL)
|
||||
if err != nil {
|
||||
errorMsg := fmt.Sprintf("failed to look up Azure environment descriptor for URL %q: {{err}}",
|
||||
environmentUrl)
|
||||
environmentURL)
|
||||
return nil, errwrap.Wrapf(errorMsg, err)
|
||||
}
|
||||
} else {
|
||||
|
@ -99,19 +99,37 @@ func NewAzureBackend(conf map[string]string, logger log.Logger) (physical.Backen
|
|||
}
|
||||
}
|
||||
|
||||
client, err := storage.NewBasicClientOnSovereignCloud(accountName, accountKey, environment)
|
||||
credential, err := azblob.NewSharedKeyCredential(accountName, accountKey)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("failed to create Azure client: {{err}}", err)
|
||||
}
|
||||
client.HTTPClient = cleanhttp.DefaultPooledClient()
|
||||
|
||||
blobClient := client.GetBlobService()
|
||||
container := blobClient.GetContainerReference(name)
|
||||
_, err = container.CreateIfNotExists(&storage.CreateContainerOptions{
|
||||
Access: storage.ContainerAccessTypePrivate,
|
||||
})
|
||||
URL, err := url.Parse(
|
||||
fmt.Sprintf("https://%s.blob.%s/%s", accountName, environment.StorageEndpointSuffix, name))
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(fmt.Sprintf("failed to create %q container: {{err}}", name), err)
|
||||
return nil, errwrap.Wrapf("failed to create Azure client: {{err}}", err)
|
||||
}
|
||||
|
||||
p := azblob.NewPipeline(credential, azblob.PipelineOptions{})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
containerURL := azblob.NewContainerURL(*URL, p)
|
||||
_, err = containerURL.GetProperties(ctx, azblob.LeaseAccessConditions{})
|
||||
if err != nil {
|
||||
var e azblob.StorageError
|
||||
if errors.As(err, &e) {
|
||||
switch e.ServiceCode() {
|
||||
case azblob.ServiceCodeContainerNotFound:
|
||||
_, err := containerURL.Create(ctx, azblob.Metadata{}, azblob.PublicAccessNone)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(fmt.Sprintf("failed to create %q container: {{err}}", name), err)
|
||||
}
|
||||
default:
|
||||
return nil, errwrap.Wrapf(fmt.Sprintf("failed to get properties for container %q: {{err}}", name), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
maxParStr, ok := conf["max_parallel"]
|
||||
|
@ -127,7 +145,7 @@ func NewAzureBackend(conf map[string]string, logger log.Logger) (physical.Backen
|
|||
}
|
||||
|
||||
a := &AzureBackend{
|
||||
container: container,
|
||||
container: &containerURL,
|
||||
logger: logger,
|
||||
permitPool: physical.NewPermitPool(maxParInt),
|
||||
}
|
||||
|
@ -142,22 +160,15 @@ func (a *AzureBackend) Put(ctx context.Context, entry *physical.Entry) error {
|
|||
return fmt.Errorf("value is bigger than the current supported limit of 4MBytes")
|
||||
}
|
||||
|
||||
blockID := base64.StdEncoding.EncodeToString([]byte("AAAA"))
|
||||
blocks := make([]storage.Block, 1)
|
||||
blocks[0] = storage.Block{ID: blockID, Status: storage.BlockStatusLatest}
|
||||
|
||||
a.permitPool.Acquire()
|
||||
defer a.permitPool.Release()
|
||||
|
||||
blob := &storage.Blob{
|
||||
Container: a.container,
|
||||
Name: entry.Key,
|
||||
}
|
||||
if err := blob.PutBlock(blockID, entry.Value, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
blobURL := a.container.NewBlockBlobURL(entry.Key)
|
||||
_, err := azblob.UploadBufferToBlockBlob(ctx, entry.Value, blobURL, azblob.UploadToBlockBlobOptions{
|
||||
BlockSize: MaxBlobSize,
|
||||
})
|
||||
|
||||
return blob.PutBlockList(blocks, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// Get is used to fetch an entry
|
||||
|
@ -167,22 +178,23 @@ func (a *AzureBackend) Get(ctx context.Context, key string) (*physical.Entry, er
|
|||
a.permitPool.Acquire()
|
||||
defer a.permitPool.Release()
|
||||
|
||||
blob := &storage.Blob{
|
||||
Container: a.container,
|
||||
Name: key,
|
||||
}
|
||||
exists, err := blob.Exists()
|
||||
blobURL := a.container.NewBlockBlobURL(key)
|
||||
res, err := blobURL.Download(ctx, 0, 0, azblob.BlobAccessConditions{}, false)
|
||||
if err != nil {
|
||||
var e azblob.StorageError
|
||||
if errors.As(err, &e) {
|
||||
switch e.ServiceCode() {
|
||||
case azblob.ServiceCodeBlobNotFound:
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, errwrap.Wrapf(fmt.Sprintf("failed to download blob %q: {{err}}", key), err)
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
reader, err := blob.Get(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reader := res.Body(azblob.RetryReaderOptions{})
|
||||
|
||||
defer reader.Close()
|
||||
data, err := ioutil.ReadAll(reader)
|
||||
|
||||
|
@ -198,15 +210,23 @@ func (a *AzureBackend) Get(ctx context.Context, key string) (*physical.Entry, er
|
|||
func (a *AzureBackend) Delete(ctx context.Context, key string) error {
|
||||
defer metrics.MeasureSince([]string{"azure", "delete"}, time.Now())
|
||||
|
||||
blob := &storage.Blob{
|
||||
Container: a.container,
|
||||
Name: key,
|
||||
}
|
||||
|
||||
a.permitPool.Acquire()
|
||||
defer a.permitPool.Release()
|
||||
|
||||
_, err := blob.DeleteIfExists(nil)
|
||||
blobURL := a.container.NewBlockBlobURL(key)
|
||||
_, err := blobURL.Delete(ctx, azblob.DeleteSnapshotsOptionInclude, azblob.BlobAccessConditions{})
|
||||
if err != nil {
|
||||
var e azblob.StorageError
|
||||
if errors.As(err, &e) {
|
||||
switch e.ServiceCode() {
|
||||
case azblob.ServiceCodeBlobNotFound:
|
||||
return nil
|
||||
default:
|
||||
return errwrap.Wrapf(fmt.Sprintf("failed to delete blob %q: {{err}}", key), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -218,20 +238,18 @@ func (a *AzureBackend) List(ctx context.Context, prefix string) ([]string, error
|
|||
a.permitPool.Acquire()
|
||||
defer a.permitPool.Release()
|
||||
|
||||
var marker string
|
||||
keys := []string{}
|
||||
for {
|
||||
list, err := a.container.ListBlobs(storage.ListBlobsParameters{
|
||||
for marker := (azblob.Marker{}); marker.NotDone(); {
|
||||
listBlob, err := a.container.ListBlobsFlatSegment(ctx, marker, azblob.ListBlobsSegmentOptions{
|
||||
Prefix: prefix,
|
||||
Marker: marker,
|
||||
MaxResults: MaxListResults,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, blob := range list.Blobs {
|
||||
key := strings.TrimPrefix(blob.Name, prefix)
|
||||
for _, blobInfo := range listBlob.Segment.BlobItems {
|
||||
key := strings.TrimPrefix(blobInfo.Name, prefix)
|
||||
if i := strings.Index(key, "/"); i == -1 {
|
||||
// file
|
||||
keys = append(keys, key)
|
||||
|
@ -241,10 +259,7 @@ func (a *AzureBackend) List(ctx context.Context, prefix string) ([]string, error
|
|||
}
|
||||
}
|
||||
|
||||
if list.NextMarker == "" {
|
||||
break
|
||||
}
|
||||
marker = list.NextMarker
|
||||
marker = listBlob.NextMarker
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
|
|
@ -3,22 +3,22 @@ package azure
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
storage "github.com/Azure/azure-sdk-for-go/storage"
|
||||
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/sdk/helper/logging"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
)
|
||||
|
||||
func environmentForCleanupClient(name string, armUrl string) (azure.Environment, error) {
|
||||
if armUrl != "" {
|
||||
return azure.EnvironmentFromURL(armUrl)
|
||||
func environmentForCleanupClient(name string, armURL string) (azure.Environment, error) {
|
||||
if armURL != "" {
|
||||
return azure.EnvironmentFromURL(armURL)
|
||||
}
|
||||
if name == "" {
|
||||
name = "AzurePublicCloud"
|
||||
|
@ -26,26 +26,34 @@ func environmentForCleanupClient(name string, armUrl string) (azure.Environment,
|
|||
return azure.EnvironmentFromName(name)
|
||||
}
|
||||
|
||||
func TestAzureBackend(t *testing.T) {
|
||||
if os.Getenv("AZURE_ACCOUNT_NAME") == "" ||
|
||||
os.Getenv("AZURE_ACCOUNT_KEY") == "" {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
func testFixture(t *testing.T) (physical.Backend, func()) {
|
||||
t.Helper()
|
||||
accountName := os.Getenv("AZURE_ACCOUNT_NAME")
|
||||
accountKey := os.Getenv("AZURE_ACCOUNT_KEY")
|
||||
environmentName := os.Getenv("AZURE_ENVIRONMENT")
|
||||
environmentUrl := os.Getenv("AZURE_ARM_ENDPOINT")
|
||||
environmentURL := os.Getenv("AZURE_ARM_ENDPOINT")
|
||||
|
||||
ts := time.Now().UnixNano()
|
||||
name := fmt.Sprintf("vault-test-%d", ts)
|
||||
|
||||
cleanupEnvironment, err := environmentForCleanupClient(environmentName, environmentUrl)
|
||||
cleanupEnvironment, err := environmentForCleanupClient(environmentName, environmentURL)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
cleanupClient, _ := storage.NewBasicClientOnSovereignCloud(accountName, accountKey, cleanupEnvironment)
|
||||
cleanupClient.HTTPClient = cleanhttp.DefaultPooledClient()
|
||||
|
||||
credential, err := azblob.NewSharedKeyCredential(accountName, accountKey)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
URL, err := url.Parse(fmt.Sprintf("https://%s.blob.%s/%s", accountName, cleanupEnvironment.StorageEndpointSuffix, name))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
p := azblob.NewPipeline(credential, azblob.PipelineOptions{})
|
||||
|
||||
containerURL := azblob.NewContainerURL(*URL, p)
|
||||
|
||||
logger := logging.NewVaultLogger(log.Debug)
|
||||
|
||||
|
@ -54,19 +62,39 @@ func TestAzureBackend(t *testing.T) {
|
|||
"accountName": accountName,
|
||||
"accountKey": accountKey,
|
||||
"environment": environmentName,
|
||||
"arm_endpoint": environmentUrl,
|
||||
"arm_endpoint": environmentURL,
|
||||
}, logger)
|
||||
|
||||
defer func() {
|
||||
blobService := cleanupClient.GetBlobService()
|
||||
container := blobService.GetContainerReference(name)
|
||||
container.DeleteIfExists(nil)
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
return backend, func() {
|
||||
ctx := context.Background()
|
||||
blobService, err := containerURL.GetProperties(ctx, azblob.LeaseAccessConditions{})
|
||||
if err != nil {
|
||||
t.Logf("failed to retrieve blob container info: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if blobService.StatusCode() == 200 {
|
||||
_, err := containerURL.Delete(ctx, azblob.ContainerAccessConditions{})
|
||||
if err != nil {
|
||||
t.Logf("clean up failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAzureBackend(t *testing.T) {
|
||||
if os.Getenv("AZURE_ACCOUNT_NAME") == "" ||
|
||||
os.Getenv("AZURE_ACCOUNT_KEY") == "" {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
backend, cleanup := testFixture(t)
|
||||
defer cleanup()
|
||||
|
||||
physical.ExerciseBackend(t, backend)
|
||||
physical.ExerciseBackend_ListPrefix(t, backend)
|
||||
}
|
||||
|
@ -77,40 +105,8 @@ func TestAzureBackend_ListPaging(t *testing.T) {
|
|||
t.SkipNow()
|
||||
}
|
||||
|
||||
accountName := os.Getenv("AZURE_ACCOUNT_NAME")
|
||||
accountKey := os.Getenv("AZURE_ACCOUNT_KEY")
|
||||
environmentName := os.Getenv("AZURE_ENVIRONMENT")
|
||||
environmentUrl := os.Getenv("AZURE_ARM_ENDPOINT")
|
||||
|
||||
ts := time.Now().UnixNano()
|
||||
name := fmt.Sprintf("vault-test-%d", ts)
|
||||
|
||||
cleanupEnvironment, err := environmentForCleanupClient(environmentName, environmentUrl)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
cleanupClient, _ := storage.NewBasicClientOnSovereignCloud(accountName, accountKey, cleanupEnvironment)
|
||||
cleanupClient.HTTPClient = cleanhttp.DefaultPooledClient()
|
||||
|
||||
logger := logging.NewVaultLogger(log.Debug)
|
||||
|
||||
backend, err := NewAzureBackend(map[string]string{
|
||||
"container": name,
|
||||
"accountName": accountName,
|
||||
"accountKey": accountKey,
|
||||
"environment": environmentName,
|
||||
"arm_endpoint": environmentUrl,
|
||||
}, logger)
|
||||
|
||||
defer func() {
|
||||
blobService := cleanupClient.GetBlobService()
|
||||
container := blobService.GetContainerReference(name)
|
||||
container.DeleteIfExists(nil)
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
backend, cleanup := testFixture(t)
|
||||
defer cleanup()
|
||||
|
||||
// by default, azure returns 5000 results in a page, load up more than that
|
||||
for i := 0; i < MaxListResults+100; i++ {
|
||||
|
@ -126,6 +122,7 @@ func TestAzureBackend_ListPaging(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(results) != MaxListResults+100 {
|
||||
t.Fatalf("expected %d, got %d", MaxListResults+100, len(results))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
|
@ -0,0 +1,284 @@
|
|||
package pipeline
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/mattn/go-ieproxy"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The Factory interface represents an object that can create its Policy object. Each HTTP request sent
|
||||
// requires that this Factory create a new instance of its Policy object.
|
||||
type Factory interface {
|
||||
New(next Policy, po *PolicyOptions) Policy
|
||||
}
|
||||
|
||||
// FactoryFunc is an adapter that allows the use of an ordinary function as a Factory interface.
|
||||
type FactoryFunc func(next Policy, po *PolicyOptions) PolicyFunc
|
||||
|
||||
// New calls f(next,po).
|
||||
func (f FactoryFunc) New(next Policy, po *PolicyOptions) Policy {
|
||||
return f(next, po)
|
||||
}
|
||||
|
||||
// The Policy interface represents a mutable Policy object created by a Factory. The object can mutate/process
|
||||
// the HTTP request and then forward it on to the next Policy object in the linked-list. The returned
|
||||
// Response goes backward through the linked-list for additional processing.
|
||||
// NOTE: Request is passed by value so changes do not change the caller's version of
|
||||
// the request. However, Request has some fields that reference mutable objects (not strings).
|
||||
// These references are copied; a deep copy is not performed. Specifically, this means that
|
||||
// you should avoid modifying the objects referred to by these fields: URL, Header, Body,
|
||||
// GetBody, TransferEncoding, Form, MultipartForm, Trailer, TLS, Cancel, and Response.
|
||||
type Policy interface {
|
||||
Do(ctx context.Context, request Request) (Response, error)
|
||||
}
|
||||
|
||||
// PolicyFunc is an adapter that allows the use of an ordinary function as a Policy interface.
|
||||
type PolicyFunc func(ctx context.Context, request Request) (Response, error)
|
||||
|
||||
// Do calls f(ctx, request).
|
||||
func (f PolicyFunc) Do(ctx context.Context, request Request) (Response, error) {
|
||||
return f(ctx, request)
|
||||
}
|
||||
|
||||
// Options configures a Pipeline's behavior.
|
||||
type Options struct {
|
||||
HTTPSender Factory // If sender is nil, then the pipeline's default client is used to send the HTTP requests.
|
||||
Log LogOptions
|
||||
}
|
||||
|
||||
// LogLevel tells a logger the minimum level to log. When code reports a log entry,
|
||||
// the LogLevel indicates the level of the log entry. The logger only records entries
|
||||
// whose level is at least the level it was told to log. See the Log* constants.
|
||||
// For example, if a logger is configured with LogError, then LogError, LogPanic,
|
||||
// and LogFatal entries will be logged; lower level entries are ignored.
|
||||
type LogLevel uint32
|
||||
|
||||
const (
|
||||
// LogNone tells a logger not to log any entries passed to it.
|
||||
LogNone LogLevel = iota
|
||||
|
||||
// LogFatal tells a logger to log all LogFatal entries passed to it.
|
||||
LogFatal
|
||||
|
||||
// LogPanic tells a logger to log all LogPanic and LogFatal entries passed to it.
|
||||
LogPanic
|
||||
|
||||
// LogError tells a logger to log all LogError, LogPanic and LogFatal entries passed to it.
|
||||
LogError
|
||||
|
||||
// LogWarning tells a logger to log all LogWarning, LogError, LogPanic and LogFatal entries passed to it.
|
||||
LogWarning
|
||||
|
||||
// LogInfo tells a logger to log all LogInfo, LogWarning, LogError, LogPanic and LogFatal entries passed to it.
|
||||
LogInfo
|
||||
|
||||
// LogDebug tells a logger to log all LogDebug, LogInfo, LogWarning, LogError, LogPanic and LogFatal entries passed to it.
|
||||
LogDebug
|
||||
)
|
||||
|
||||
// LogOptions configures the pipeline's logging mechanism & level filtering.
|
||||
type LogOptions struct {
|
||||
Log func(level LogLevel, message string)
|
||||
|
||||
// ShouldLog is called periodically allowing you to return whether the specified LogLevel should be logged or not.
|
||||
// An application can return different values over the its lifetime; this allows the application to dynamically
|
||||
// alter what is logged. NOTE: This method can be called by multiple goroutines simultaneously so make sure
|
||||
// you implement it in a goroutine-safe way. If nil, nothing is logged (the equivalent of returning LogNone).
|
||||
// Usually, the function will be implemented simply like this: return level <= LogWarning
|
||||
ShouldLog func(level LogLevel) bool
|
||||
}
|
||||
|
||||
type pipeline struct {
|
||||
factories []Factory
|
||||
options Options
|
||||
}
|
||||
|
||||
// The Pipeline interface represents an ordered list of Factory objects and an object implementing the HTTPSender interface.
|
||||
// You construct a Pipeline by calling the pipeline.NewPipeline function. To send an HTTP request, call pipeline.NewRequest
|
||||
// and then call Pipeline's Do method passing a context, the request, and a method-specific Factory (or nil). Passing a
|
||||
// method-specific Factory allows this one call to Do to inject a Policy into the linked-list. The policy is injected where
|
||||
// the MethodFactoryMarker (see the pipeline.MethodFactoryMarker function) is in the slice of Factory objects.
|
||||
//
|
||||
// When Do is called, the Pipeline object asks each Factory object to construct its Policy object and adds each Policy to a linked-list.
|
||||
// THen, Do sends the Context and Request through all the Policy objects. The final Policy object sends the request over the network
|
||||
// (via the HTTPSender object passed to NewPipeline) and the response is returned backwards through all the Policy objects.
|
||||
// Since Pipeline and Factory objects are goroutine-safe, you typically create 1 Pipeline object and reuse it to make many HTTP requests.
|
||||
type Pipeline interface {
|
||||
Do(ctx context.Context, methodFactory Factory, request Request) (Response, error)
|
||||
}
|
||||
|
||||
// NewPipeline creates a new goroutine-safe Pipeline object from the slice of Factory objects and the specified options.
|
||||
func NewPipeline(factories []Factory, o Options) Pipeline {
|
||||
if o.HTTPSender == nil {
|
||||
o.HTTPSender = newDefaultHTTPClientFactory()
|
||||
}
|
||||
if o.Log.Log == nil {
|
||||
o.Log.Log = func(LogLevel, string) {} // No-op logger
|
||||
}
|
||||
return &pipeline{factories: factories, options: o}
|
||||
}
|
||||
|
||||
// Do is called for each and every HTTP request. It tells each Factory to create its own (mutable) Policy object
|
||||
// replacing a MethodFactoryMarker factory (if it exists) with the methodFactory passed in. Then, the Context and Request
|
||||
// are sent through the pipeline of Policy objects (which can transform the Request's URL/query parameters/headers) and
|
||||
// ultimately sends the transformed HTTP request over the network.
|
||||
func (p *pipeline) Do(ctx context.Context, methodFactory Factory, request Request) (Response, error) {
|
||||
response, err := p.newPolicies(methodFactory).Do(ctx, request)
|
||||
request.close()
|
||||
return response, err
|
||||
}
|
||||
|
||||
func (p *pipeline) newPolicies(methodFactory Factory) Policy {
|
||||
// The last Policy is the one that actually sends the request over the wire and gets the response.
|
||||
// It is overridable via the Options' HTTPSender field.
|
||||
po := &PolicyOptions{pipeline: p} // One object shared by all policy objects
|
||||
next := p.options.HTTPSender.New(nil, po)
|
||||
|
||||
// Walk over the slice of Factory objects in reverse (from wire to API)
|
||||
markers := 0
|
||||
for i := len(p.factories) - 1; i >= 0; i-- {
|
||||
factory := p.factories[i]
|
||||
if _, ok := factory.(methodFactoryMarker); ok {
|
||||
markers++
|
||||
if markers > 1 {
|
||||
panic("MethodFactoryMarker can only appear once in the pipeline")
|
||||
}
|
||||
if methodFactory != nil {
|
||||
// Replace MethodFactoryMarker with passed-in methodFactory
|
||||
next = methodFactory.New(next, po)
|
||||
}
|
||||
} else {
|
||||
// Use the slice's Factory to construct its Policy
|
||||
next = factory.New(next, po)
|
||||
}
|
||||
}
|
||||
|
||||
// Each Factory has created its Policy
|
||||
if markers == 0 && methodFactory != nil {
|
||||
panic("Non-nil methodFactory requires MethodFactoryMarker in the pipeline")
|
||||
}
|
||||
return next // Return head of the Policy object linked-list
|
||||
}
|
||||
|
||||
// A PolicyOptions represents optional information that can be used by a node in the
|
||||
// linked-list of Policy objects. A PolicyOptions is passed to the Factory's New method
|
||||
// which passes it (if desired) to the Policy object it creates. Today, the Policy object
|
||||
// uses the options to perform logging. But, in the future, this could be used for more.
|
||||
type PolicyOptions struct {
|
||||
pipeline *pipeline
|
||||
}
|
||||
|
||||
// ShouldLog returns true if the specified log level should be logged.
|
||||
func (po *PolicyOptions) ShouldLog(level LogLevel) bool {
|
||||
if po.pipeline.options.Log.ShouldLog != nil {
|
||||
return po.pipeline.options.Log.ShouldLog(level)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Log logs a string to the Pipeline's Logger.
|
||||
func (po *PolicyOptions) Log(level LogLevel, msg string) {
|
||||
if !po.ShouldLog(level) {
|
||||
return // Short circuit message formatting if we're not logging it
|
||||
}
|
||||
|
||||
// We are logging it, ensure trailing newline
|
||||
if len(msg) == 0 || msg[len(msg)-1] != '\n' {
|
||||
msg += "\n" // Ensure trailing newline
|
||||
}
|
||||
po.pipeline.options.Log.Log(level, msg)
|
||||
|
||||
// If logger doesn't handle fatal/panic, we'll do it here.
|
||||
if level == LogFatal {
|
||||
os.Exit(1)
|
||||
} else if level == LogPanic {
|
||||
panic(msg)
|
||||
}
|
||||
}
|
||||
|
||||
var pipelineHTTPClient = newDefaultHTTPClient()
|
||||
|
||||
func newDefaultHTTPClient() *http.Client {
|
||||
// We want the Transport to have a large connection pool
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: ieproxy.GetProxyFunc(),
|
||||
// We use Dial instead of DialContext as DialContext has been reported to cause slower performance.
|
||||
Dial /*Context*/ : (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).Dial, /*Context*/
|
||||
MaxIdleConns: 0, // No limit
|
||||
MaxIdleConnsPerHost: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
DisableKeepAlives: false,
|
||||
DisableCompression: false,
|
||||
MaxResponseHeaderBytes: 0,
|
||||
//ResponseHeaderTimeout: time.Duration{},
|
||||
//ExpectContinueTimeout: time.Duration{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// newDefaultHTTPClientFactory creates a DefaultHTTPClientPolicyFactory object that sends HTTP requests to a Go's default http.Client.
|
||||
func newDefaultHTTPClientFactory() Factory {
|
||||
return FactoryFunc(func(next Policy, po *PolicyOptions) PolicyFunc {
|
||||
return func(ctx context.Context, request Request) (Response, error) {
|
||||
r, err := pipelineHTTPClient.Do(request.WithContext(ctx))
|
||||
if err != nil {
|
||||
err = NewError(err, "HTTP request failed")
|
||||
}
|
||||
return NewHTTPResponse(r), err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var mfm = methodFactoryMarker{} // Singleton
|
||||
|
||||
// MethodFactoryMarker returns a special marker Factory object. When Pipeline's Do method is called, any
|
||||
// MethodMarkerFactory object is replaced with the specified methodFactory object. If nil is passed fro Do's
|
||||
// methodFactory parameter, then the MethodFactoryMarker is ignored as the linked-list of Policy objects is created.
|
||||
func MethodFactoryMarker() Factory {
|
||||
return mfm
|
||||
}
|
||||
|
||||
type methodFactoryMarker struct {
|
||||
}
|
||||
|
||||
func (methodFactoryMarker) New(next Policy, po *PolicyOptions) Policy {
|
||||
panic("methodFactoryMarker policy should have been replaced with a method policy")
|
||||
}
|
||||
|
||||
// LogSanitizer can be implemented to clean secrets from lines logged by ForceLog
|
||||
// By default no implemetation is provided here, because pipeline may be used in many different
|
||||
// contexts, so the correct implementation is context-dependent
|
||||
type LogSanitizer interface {
|
||||
SanitizeLogMessage(raw string) string
|
||||
}
|
||||
|
||||
var sanitizer LogSanitizer
|
||||
var enableForceLog bool = true
|
||||
|
||||
// SetLogSanitizer can be called to supply a custom LogSanitizer.
|
||||
// There is no threadsafety or locking on the underlying variable,
|
||||
// so call this function just once at startup of your application
|
||||
// (Don't later try to change the sanitizer on the fly).
|
||||
func SetLogSanitizer(s LogSanitizer)(){
|
||||
sanitizer = s
|
||||
}
|
||||
|
||||
// SetForceLogEnabled can be used to disable ForceLog
|
||||
// There is no threadsafety or locking on the underlying variable,
|
||||
// so call this function just once at startup of your application
|
||||
// (Don't later try to change the setting on the fly).
|
||||
func SetForceLogEnabled(enable bool)() {
|
||||
enableForceLog = enable
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package pipeline
|
||||
|
||||
|
||||
// ForceLog should rarely be used. It forceable logs an entry to the
|
||||
// Windows Event Log (on Windows) or to the SysLog (on Linux)
|
||||
func ForceLog(level LogLevel, msg string) {
|
||||
if !enableForceLog {
|
||||
return
|
||||
}
|
||||
if sanitizer != nil {
|
||||
msg = sanitizer.SanitizeLogMessage(msg)
|
||||
}
|
||||
forceLog(level, msg)
|
||||
}
|
33
vendor/github.com/Azure/azure-pipeline-go/pipeline/defaultlog_syslog.go
generated
vendored
Normal file
33
vendor/github.com/Azure/azure-pipeline-go/pipeline/defaultlog_syslog.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
// +build !windows,!nacl,!plan9
|
||||
|
||||
package pipeline
|
||||
|
||||
import (
|
||||
"log"
|
||||
"log/syslog"
|
||||
)
|
||||
|
||||
// forceLog should rarely be used. It forceable logs an entry to the
|
||||
// Windows Event Log (on Windows) or to the SysLog (on Linux)
|
||||
func forceLog(level LogLevel, msg string) {
|
||||
if defaultLogger == nil {
|
||||
return // Return fast if we failed to create the logger.
|
||||
}
|
||||
// We are logging it, ensure trailing newline
|
||||
if len(msg) == 0 || msg[len(msg)-1] != '\n' {
|
||||
msg += "\n" // Ensure trailing newline
|
||||
}
|
||||
switch level {
|
||||
case LogFatal:
|
||||
defaultLogger.Fatal(msg)
|
||||
case LogPanic:
|
||||
defaultLogger.Panic(msg)
|
||||
case LogError, LogWarning, LogInfo:
|
||||
defaultLogger.Print(msg)
|
||||
}
|
||||
}
|
||||
|
||||
var defaultLogger = func() *log.Logger {
|
||||
l, _ := syslog.NewLogger(syslog.LOG_USER|syslog.LOG_WARNING, log.LstdFlags)
|
||||
return l
|
||||
}()
|
61
vendor/github.com/Azure/azure-pipeline-go/pipeline/defaultlog_windows.go
generated
vendored
Normal file
61
vendor/github.com/Azure/azure-pipeline-go/pipeline/defaultlog_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,61 @@
|
|||
package pipeline
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// forceLog should rarely be used. It forceable logs an entry to the
|
||||
// Windows Event Log (on Windows) or to the SysLog (on Linux)
|
||||
func forceLog(level LogLevel, msg string) {
|
||||
var el eventType
|
||||
switch level {
|
||||
case LogError, LogFatal, LogPanic:
|
||||
el = elError
|
||||
case LogWarning:
|
||||
el = elWarning
|
||||
case LogInfo:
|
||||
el = elInfo
|
||||
}
|
||||
// We are logging it, ensure trailing newline
|
||||
if len(msg) == 0 || msg[len(msg)-1] != '\n' {
|
||||
msg += "\n" // Ensure trailing newline
|
||||
}
|
||||
reportEvent(el, 0, msg)
|
||||
}
|
||||
|
||||
type eventType int16
|
||||
|
||||
const (
|
||||
elSuccess eventType = 0
|
||||
elError eventType = 1
|
||||
elWarning eventType = 2
|
||||
elInfo eventType = 4
|
||||
)
|
||||
|
||||
var reportEvent = func() func(eventType eventType, eventID int32, msg string) {
|
||||
advAPI32 := syscall.MustLoadDLL("advapi32.dll") // lower case to tie in with Go's sysdll registration
|
||||
registerEventSource := advAPI32.MustFindProc("RegisterEventSourceW")
|
||||
|
||||
sourceName, _ := os.Executable()
|
||||
sourceNameUTF16, _ := syscall.UTF16PtrFromString(sourceName)
|
||||
handle, _, lastErr := registerEventSource.Call(uintptr(0), uintptr(unsafe.Pointer(sourceNameUTF16)))
|
||||
if lastErr == nil { // On error, logging is a no-op
|
||||
return func(eventType eventType, eventID int32, msg string) {}
|
||||
}
|
||||
reportEvent := advAPI32.MustFindProc("ReportEventW")
|
||||
return func(eventType eventType, eventID int32, msg string) {
|
||||
s, _ := syscall.UTF16PtrFromString(msg)
|
||||
_, _, _ = reportEvent.Call(
|
||||
uintptr(handle), // HANDLE hEventLog
|
||||
uintptr(eventType), // WORD wType
|
||||
uintptr(0), // WORD wCategory
|
||||
uintptr(eventID), // DWORD dwEventID
|
||||
uintptr(0), // PSID lpUserSid
|
||||
uintptr(1), // WORD wNumStrings
|
||||
uintptr(0), // DWORD dwDataSize
|
||||
uintptr(unsafe.Pointer(&s)), // LPCTSTR *lpStrings
|
||||
uintptr(0)) // LPVOID lpRawData
|
||||
}
|
||||
}()
|
|
@ -0,0 +1,161 @@
|
|||
// Copyright 2017 Microsoft Corporation. All rights reserved.
|
||||
// Use of this source code is governed by an MIT
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package pipeline implements an HTTP request/response middleware pipeline whose
|
||||
policy objects mutate an HTTP request's URL, query parameters, and/or headers before
|
||||
the request is sent over the wire.
|
||||
|
||||
Not all policy objects mutate an HTTP request; some policy objects simply impact the
|
||||
flow of requests/responses by performing operations such as logging, retry policies,
|
||||
timeouts, failure injection, and deserialization of response payloads.
|
||||
|
||||
Implementing the Policy Interface
|
||||
|
||||
To implement a policy, define a struct that implements the pipeline.Policy interface's Do method. Your Do
|
||||
method is called when an HTTP request wants to be sent over the network. Your Do method can perform any
|
||||
operation(s) it desires. For example, it can log the outgoing request, mutate the URL, headers, and/or query
|
||||
parameters, inject a failure, etc. Your Do method must then forward the HTTP request to next Policy object
|
||||
in a linked-list ensuring that the remaining Policy objects perform their work. Ultimately, the last Policy
|
||||
object sends the HTTP request over the network (by calling the HTTPSender's Do method).
|
||||
|
||||
When an HTTP response comes back, each Policy object in the linked-list gets a chance to process the response
|
||||
(in reverse order). The Policy object can log the response, retry the operation if due to a transient failure
|
||||
or timeout, deserialize the response body, etc. Ultimately, the last Policy object returns the HTTP response
|
||||
to the code that initiated the original HTTP request.
|
||||
|
||||
Here is a template for how to define a pipeline.Policy object:
|
||||
|
||||
type myPolicy struct {
|
||||
node PolicyNode
|
||||
// TODO: Add configuration/setting fields here (if desired)...
|
||||
}
|
||||
|
||||
func (p *myPolicy) Do(ctx context.Context, request pipeline.Request) (pipeline.Response, error) {
|
||||
// TODO: Mutate/process the HTTP request here...
|
||||
response, err := p.node.Do(ctx, request) // Forward HTTP request to next Policy & get HTTP response
|
||||
// TODO: Mutate/process the HTTP response here...
|
||||
return response, err // Return response/error to previous Policy
|
||||
}
|
||||
|
||||
Implementing the Factory Interface
|
||||
|
||||
Each Policy struct definition requires a factory struct definition that implements the pipeline.Factory interface's New
|
||||
method. The New method is called when application code wants to initiate a new HTTP request. Factory's New method is
|
||||
passed a pipeline.PolicyNode object which contains a reference to the owning pipeline.Pipeline object (discussed later) and
|
||||
a reference to the next Policy object in the linked list. The New method should create its corresponding Policy object
|
||||
passing it the PolicyNode and any other configuration/settings fields appropriate for the specific Policy object.
|
||||
|
||||
Here is a template for how to define a pipeline.Policy object:
|
||||
|
||||
// NOTE: Once created & initialized, Factory objects should be goroutine-safe (ex: immutable);
|
||||
// this allows reuse (efficient use of memory) and makes these objects usable by multiple goroutines concurrently.
|
||||
type myPolicyFactory struct {
|
||||
// TODO: Add any configuration/setting fields if desired...
|
||||
}
|
||||
|
||||
func (f *myPolicyFactory) New(node pipeline.PolicyNode) Policy {
|
||||
return &myPolicy{node: node} // TODO: Also initialize any configuration/setting fields here (if desired)...
|
||||
}
|
||||
|
||||
Using your Factory and Policy objects via a Pipeline
|
||||
|
||||
To use the Factory and Policy objects, an application constructs a slice of Factory objects and passes
|
||||
this slice to the pipeline.NewPipeline function.
|
||||
|
||||
func NewPipeline(factories []pipeline.Factory, sender pipeline.HTTPSender) Pipeline
|
||||
|
||||
This function also requires an object implementing the HTTPSender interface. For simple scenarios,
|
||||
passing nil for HTTPSender causes a standard Go http.Client object to be created and used to actually
|
||||
send the HTTP response over the network. For more advanced scenarios, you can pass your own HTTPSender
|
||||
object in. This allows sharing of http.Client objects or the use of custom-configured http.Client objects
|
||||
or other objects that can simulate the network requests for testing purposes.
|
||||
|
||||
Now that you have a pipeline.Pipeline object, you can create a pipeline.Request object (which is a simple
|
||||
wrapper around Go's standard http.Request object) and pass it to Pipeline's Do method along with passing a
|
||||
context.Context for cancelling the HTTP request (if desired).
|
||||
|
||||
type Pipeline interface {
|
||||
Do(ctx context.Context, methodFactory pipeline.Factory, request pipeline.Request) (pipeline.Response, error)
|
||||
}
|
||||
|
||||
Do iterates over the slice of Factory objects and tells each one to create its corresponding
|
||||
Policy object. After the linked-list of Policy objects have been created, Do calls the first
|
||||
Policy object passing it the Context & HTTP request parameters. These parameters now flow through
|
||||
all the Policy objects giving each object a chance to look at and/or mutate the HTTP request.
|
||||
The last Policy object sends the message over the network.
|
||||
|
||||
When the network operation completes, the HTTP response and error return values pass
|
||||
back through the same Policy objects in reverse order. Most Policy objects ignore the
|
||||
response/error but some log the result, retry the operation (depending on the exact
|
||||
reason the operation failed), or deserialize the response's body. Your own Policy
|
||||
objects can do whatever they like when processing outgoing requests or incoming responses.
|
||||
|
||||
Note that after an I/O request runs to completion, the Policy objects for that request
|
||||
are garbage collected. However, Pipeline object (like Factory objects) are goroutine-safe allowing
|
||||
them to be created once and reused over many I/O operations. This allows for efficient use of
|
||||
memory and also makes them safely usable by multiple goroutines concurrently.
|
||||
|
||||
Inserting a Method-Specific Factory into the Linked-List of Policy Objects
|
||||
|
||||
While Pipeline and Factory objects can be reused over many different operations, it is
|
||||
common to have special behavior for a specific operation/method. For example, a method
|
||||
may need to deserialize the response's body to an instance of a specific data type.
|
||||
To accommodate this, the Pipeline's Do method takes an additional method-specific
|
||||
Factory object. The Do method tells this Factory to create a Policy object and
|
||||
injects this method-specific Policy object into the linked-list of Policy objects.
|
||||
|
||||
When creating a Pipeline object, the slice of Factory objects passed must have 1
|
||||
(and only 1) entry marking where the method-specific Factory should be injected.
|
||||
The Factory marker is obtained by calling the pipeline.MethodFactoryMarker() function:
|
||||
|
||||
func MethodFactoryMarker() pipeline.Factory
|
||||
|
||||
Creating an HTTP Request Object
|
||||
|
||||
The HTTP request object passed to Pipeline's Do method is not Go's http.Request struct.
|
||||
Instead, it is a pipeline.Request struct which is a simple wrapper around Go's standard
|
||||
http.Request. You create a pipeline.Request object by calling the pipeline.NewRequest function:
|
||||
|
||||
func NewRequest(method string, url url.URL, options pipeline.RequestOptions) (request pipeline.Request, err error)
|
||||
|
||||
To this function, you must pass a pipeline.RequestOptions that looks like this:
|
||||
|
||||
type RequestOptions struct {
|
||||
// The readable and seekable stream to be sent to the server as the request's body.
|
||||
Body io.ReadSeeker
|
||||
|
||||
// The callback method (if not nil) to be invoked to report progress as the stream is uploaded in the HTTP request.
|
||||
Progress ProgressReceiver
|
||||
}
|
||||
|
||||
The method and struct ensure that the request's body stream is a read/seekable stream.
|
||||
A seekable stream is required so that upon retry, the final Policy object can seek
|
||||
the stream back to the beginning before retrying the network request and re-uploading the
|
||||
body. In addition, you can associate a ProgressReceiver callback function which will be
|
||||
invoked periodically to report progress while bytes are being read from the body stream
|
||||
and sent over the network.
|
||||
|
||||
Processing the HTTP Response
|
||||
|
||||
When an HTTP response comes in from the network, a reference to Go's http.Response struct is
|
||||
embedded in a struct that implements the pipeline.Response interface:
|
||||
|
||||
type Response interface {
|
||||
Response() *http.Response
|
||||
}
|
||||
|
||||
This interface is returned through all the Policy objects. Each Policy object can call the Response
|
||||
interface's Response method to examine (or mutate) the embedded http.Response object.
|
||||
|
||||
A Policy object can internally define another struct (implementing the pipeline.Response interface)
|
||||
that embeds an http.Response and adds additional fields and return this structure to other Policy
|
||||
objects. This allows a Policy object to deserialize the body to some other struct and return the
|
||||
original http.Response and the additional struct back through the Policy chain. Other Policy objects
|
||||
can see the Response but cannot see the additional struct with the deserialized body. After all the
|
||||
Policy objects have returned, the pipeline.Response interface is returned by Pipeline's Do method.
|
||||
The caller of this method can perform a type assertion attempting to get back to the struct type
|
||||
really returned by the Policy object. If the type assertion is successful, the caller now has
|
||||
access to both the http.Response and the deserialized struct object.*/
|
||||
package pipeline
|
|
@ -0,0 +1,181 @@
|
|||
package pipeline
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
func errorWithPC(msg string, pc uintptr) string {
|
||||
s := ""
|
||||
if fn := runtime.FuncForPC(pc); fn != nil {
|
||||
file, line := fn.FileLine(pc)
|
||||
s = fmt.Sprintf("-> %v, %v:%v\n", fn.Name(), file, line)
|
||||
}
|
||||
s += msg + "\n\n"
|
||||
return s
|
||||
}
|
||||
|
||||
func getPC(callersToSkip int) uintptr {
|
||||
// Get the PC of Initialize method's caller.
|
||||
pc := [1]uintptr{}
|
||||
_ = runtime.Callers(callersToSkip, pc[:])
|
||||
return pc[0]
|
||||
}
|
||||
|
||||
// ErrorNode can be an embedded field in a private error object. This field
|
||||
// adds Program Counter support and a 'cause' (reference to a preceding error).
|
||||
// When initializing a error type with this embedded field, initialize the
|
||||
// ErrorNode field by calling ErrorNode{}.Initialize(cause).
|
||||
type ErrorNode struct {
|
||||
pc uintptr // Represents a Program Counter that you can get symbols for.
|
||||
cause error // Refers to the preceding error (or nil)
|
||||
}
|
||||
|
||||
// Error returns a string with the PC's symbols or "" if the PC is invalid.
|
||||
// When defining a new error type, have its Error method call this one passing
|
||||
// it the string representation of the error.
|
||||
func (e *ErrorNode) Error(msg string) string {
|
||||
s := errorWithPC(msg, e.pc)
|
||||
if e.cause != nil {
|
||||
s += e.cause.Error() + "\n"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Cause returns the error that preceded this error.
|
||||
func (e *ErrorNode) Cause() error { return e.cause }
|
||||
|
||||
// Temporary returns true if the error occurred due to a temporary condition.
|
||||
func (e ErrorNode) Temporary() bool {
|
||||
type temporary interface {
|
||||
Temporary() bool
|
||||
}
|
||||
|
||||
for err := e.cause; err != nil; {
|
||||
if t, ok := err.(temporary); ok {
|
||||
return t.Temporary()
|
||||
}
|
||||
|
||||
if cause, ok := err.(causer); ok {
|
||||
err = cause.Cause()
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Timeout returns true if the error occurred due to time expiring.
|
||||
func (e ErrorNode) Timeout() bool {
|
||||
type timeout interface {
|
||||
Timeout() bool
|
||||
}
|
||||
|
||||
for err := e.cause; err != nil; {
|
||||
if t, ok := err.(timeout); ok {
|
||||
return t.Timeout()
|
||||
}
|
||||
|
||||
if cause, ok := err.(causer); ok {
|
||||
err = cause.Cause()
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Initialize is used to initialize an embedded ErrorNode field.
|
||||
// It captures the caller's program counter and saves the cause (preceding error).
|
||||
// To initialize the field, use "ErrorNode{}.Initialize(cause, 3)". A callersToSkip
|
||||
// value of 3 is very common; but, depending on your code nesting, you may need
|
||||
// a different value.
|
||||
func (ErrorNode) Initialize(cause error, callersToSkip int) ErrorNode {
|
||||
pc := getPC(callersToSkip)
|
||||
return ErrorNode{pc: pc, cause: cause}
|
||||
}
|
||||
|
||||
// Cause walks all the preceding errors and return the originating error.
|
||||
func Cause(err error) error {
|
||||
for err != nil {
|
||||
cause, ok := err.(causer)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
err = cause.Cause()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ErrorNodeNoCause can be an embedded field in a private error object. This field
|
||||
// adds Program Counter support.
|
||||
// When initializing a error type with this embedded field, initialize the
|
||||
// ErrorNodeNoCause field by calling ErrorNodeNoCause{}.Initialize().
|
||||
type ErrorNodeNoCause struct {
|
||||
pc uintptr // Represents a Program Counter that you can get symbols for.
|
||||
}
|
||||
|
||||
// Error returns a string with the PC's symbols or "" if the PC is invalid.
|
||||
// When defining a new error type, have its Error method call this one passing
|
||||
// it the string representation of the error.
|
||||
func (e *ErrorNodeNoCause) Error(msg string) string {
|
||||
return errorWithPC(msg, e.pc)
|
||||
}
|
||||
|
||||
// Temporary returns true if the error occurred due to a temporary condition.
|
||||
func (e ErrorNodeNoCause) Temporary() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Timeout returns true if the error occurred due to time expiring.
|
||||
func (e ErrorNodeNoCause) Timeout() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Initialize is used to initialize an embedded ErrorNode field.
|
||||
// It captures the caller's program counter.
|
||||
// To initialize the field, use "ErrorNodeNoCause{}.Initialize(3)". A callersToSkip
|
||||
// value of 3 is very common; but, depending on your code nesting, you may need
|
||||
// a different value.
|
||||
func (ErrorNodeNoCause) Initialize(callersToSkip int) ErrorNodeNoCause {
|
||||
pc := getPC(callersToSkip)
|
||||
return ErrorNodeNoCause{pc: pc}
|
||||
}
|
||||
|
||||
// NewError creates a simple string error (like Error.New). But, this
|
||||
// error also captures the caller's Program Counter and the preceding error (if provided).
|
||||
func NewError(cause error, msg string) error {
|
||||
if cause != nil {
|
||||
return &pcError{
|
||||
ErrorNode: ErrorNode{}.Initialize(cause, 3),
|
||||
msg: msg,
|
||||
}
|
||||
}
|
||||
return &pcErrorNoCause{
|
||||
ErrorNodeNoCause: ErrorNodeNoCause{}.Initialize(3),
|
||||
msg: msg,
|
||||
}
|
||||
}
|
||||
|
||||
// pcError is a simple string error (like error.New) with an ErrorNode (PC & cause).
|
||||
type pcError struct {
|
||||
ErrorNode
|
||||
msg string
|
||||
}
|
||||
|
||||
// Error satisfies the error interface. It shows the error with Program Counter
|
||||
// symbols and calls Error on the preceding error so you can see the full error chain.
|
||||
func (e *pcError) Error() string { return e.ErrorNode.Error(e.msg) }
|
||||
|
||||
// pcErrorNoCause is a simple string error (like error.New) with an ErrorNode (PC).
|
||||
type pcErrorNoCause struct {
|
||||
ErrorNodeNoCause
|
||||
msg string
|
||||
}
|
||||
|
||||
// Error satisfies the error interface. It shows the error with Program Counter symbols.
|
||||
func (e *pcErrorNoCause) Error() string { return e.ErrorNodeNoCause.Error(e.msg) }
|
|
@ -0,0 +1,82 @@
|
|||
package pipeline
|
||||
|
||||
import "io"
|
||||
|
||||
// ********** The following is common between the request body AND the response body.
|
||||
|
||||
// ProgressReceiver defines the signature of a callback function invoked as progress is reported.
|
||||
type ProgressReceiver func(bytesTransferred int64)
|
||||
|
||||
// ********** The following are specific to the request body (a ReadSeekCloser)
|
||||
|
||||
// This struct is used when sending a body to the network
|
||||
type requestBodyProgress struct {
|
||||
requestBody io.ReadSeeker // Seeking is required to support retries
|
||||
pr ProgressReceiver
|
||||
}
|
||||
|
||||
// NewRequestBodyProgress adds progress reporting to an HTTP request's body stream.
|
||||
func NewRequestBodyProgress(requestBody io.ReadSeeker, pr ProgressReceiver) io.ReadSeeker {
|
||||
if pr == nil {
|
||||
panic("pr must not be nil")
|
||||
}
|
||||
return &requestBodyProgress{requestBody: requestBody, pr: pr}
|
||||
}
|
||||
|
||||
// Read reads a block of data from an inner stream and reports progress
|
||||
func (rbp *requestBodyProgress) Read(p []byte) (n int, err error) {
|
||||
n, err = rbp.requestBody.Read(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Invokes the user's callback method to report progress
|
||||
position, err := rbp.requestBody.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rbp.pr(position)
|
||||
return
|
||||
}
|
||||
|
||||
func (rbp *requestBodyProgress) Seek(offset int64, whence int) (offsetFromStart int64, err error) {
|
||||
return rbp.requestBody.Seek(offset, whence)
|
||||
}
|
||||
|
||||
// requestBodyProgress supports Close but the underlying stream may not; if it does, Close will close it.
|
||||
func (rbp *requestBodyProgress) Close() error {
|
||||
if c, ok := rbp.requestBody.(io.Closer); ok {
|
||||
return c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ********** The following are specific to the response body (a ReadCloser)
|
||||
|
||||
// This struct is used when sending a body to the network
|
||||
type responseBodyProgress struct {
|
||||
responseBody io.ReadCloser
|
||||
pr ProgressReceiver
|
||||
offset int64
|
||||
}
|
||||
|
||||
// NewResponseBodyProgress adds progress reporting to an HTTP response's body stream.
|
||||
func NewResponseBodyProgress(responseBody io.ReadCloser, pr ProgressReceiver) io.ReadCloser {
|
||||
if pr == nil {
|
||||
panic("pr must not be nil")
|
||||
}
|
||||
return &responseBodyProgress{responseBody: responseBody, pr: pr, offset: 0}
|
||||
}
|
||||
|
||||
// Read reads a block of data from an inner stream and reports progress
|
||||
func (rbp *responseBodyProgress) Read(p []byte) (n int, err error) {
|
||||
n, err = rbp.responseBody.Read(p)
|
||||
rbp.offset += int64(n)
|
||||
|
||||
// Invokes the user's callback method to report progress
|
||||
rbp.pr(rbp.offset)
|
||||
return
|
||||
}
|
||||
|
||||
func (rbp *responseBodyProgress) Close() error {
|
||||
return rbp.responseBody.Close()
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
package pipeline
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Request is a thin wrapper over an http.Request. The wrapper provides several helper methods.
|
||||
type Request struct {
|
||||
*http.Request
|
||||
}
|
||||
|
||||
// NewRequest initializes a new HTTP request object with any desired options.
|
||||
func NewRequest(method string, url url.URL, body io.ReadSeeker) (request Request, err error) {
|
||||
// Note: the url is passed by value so that any pipeline operations that modify it do so on a copy.
|
||||
|
||||
// This code to construct an http.Request is copied from http.NewRequest(); we intentionally omitted removeEmptyPort for now.
|
||||
request.Request = &http.Request{
|
||||
Method: method,
|
||||
URL: &url,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: make(http.Header),
|
||||
Host: url.Host,
|
||||
}
|
||||
|
||||
if body != nil {
|
||||
err = request.SetBody(body)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SetBody sets the body and content length, assumes body is not nil.
|
||||
func (r Request) SetBody(body io.ReadSeeker) error {
|
||||
size, err := body.Seek(0, io.SeekEnd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body.Seek(0, io.SeekStart)
|
||||
r.ContentLength = size
|
||||
r.Header["Content-Length"] = []string{strconv.FormatInt(size, 10)}
|
||||
|
||||
if size != 0 {
|
||||
r.Body = &retryableRequestBody{body: body}
|
||||
r.GetBody = func() (io.ReadCloser, error) {
|
||||
_, err := body.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.Body, nil
|
||||
}
|
||||
} else {
|
||||
// in case the body is an empty stream, we need to use http.NoBody to explicitly provide no content
|
||||
r.Body = http.NoBody
|
||||
r.GetBody = func() (io.ReadCloser, error) {
|
||||
return http.NoBody, nil
|
||||
}
|
||||
|
||||
// close the user-provided empty body
|
||||
if c, ok := body.(io.Closer); ok {
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy makes a copy of an http.Request. Specifically, it makes a deep copy
|
||||
// of its Method, URL, Host, Proto(Major/Minor), Header. ContentLength, Close,
|
||||
// RemoteAddr, RequestURI. Copy makes a shallow copy of the Body, GetBody, TLS,
|
||||
// Cancel, Response, and ctx fields. Copy panics if any of these fields are
|
||||
// not nil: TransferEncoding, Form, PostForm, MultipartForm, or Trailer.
|
||||
func (r Request) Copy() Request {
|
||||
if r.TransferEncoding != nil || r.Form != nil || r.PostForm != nil || r.MultipartForm != nil || r.Trailer != nil {
|
||||
panic("Can't make a deep copy of the http.Request because at least one of the following is not nil:" +
|
||||
"TransferEncoding, Form, PostForm, MultipartForm, or Trailer.")
|
||||
}
|
||||
copy := *r.Request // Copy the request
|
||||
urlCopy := *(r.Request.URL) // Copy the URL
|
||||
copy.URL = &urlCopy
|
||||
copy.Header = http.Header{} // Copy the header
|
||||
for k, vs := range r.Header {
|
||||
for _, value := range vs {
|
||||
copy.Header.Add(k, value)
|
||||
}
|
||||
}
|
||||
return Request{Request: ©} // Return the copy
|
||||
}
|
||||
|
||||
func (r Request) close() error {
|
||||
if r.Body != nil && r.Body != http.NoBody {
|
||||
c, ok := r.Body.(*retryableRequestBody)
|
||||
if !ok {
|
||||
panic("unexpected request body type (should be *retryableReadSeekerCloser)")
|
||||
}
|
||||
return c.realClose()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RewindBody seeks the request's Body stream back to the beginning so it can be resent when retrying an operation.
|
||||
func (r Request) RewindBody() error {
|
||||
if r.Body != nil && r.Body != http.NoBody {
|
||||
s, ok := r.Body.(io.Seeker)
|
||||
if !ok {
|
||||
panic("unexpected request body type (should be io.Seeker)")
|
||||
}
|
||||
|
||||
// Reset the stream back to the beginning
|
||||
_, err := s.Seek(0, io.SeekStart)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ********** The following type/methods implement the retryableRequestBody (a ReadSeekCloser)
|
||||
|
||||
// This struct is used when sending a body to the network
|
||||
type retryableRequestBody struct {
|
||||
body io.ReadSeeker // Seeking is required to support retries
|
||||
}
|
||||
|
||||
// Read reads a block of data from an inner stream and reports progress
|
||||
func (b *retryableRequestBody) Read(p []byte) (n int, err error) {
|
||||
return b.body.Read(p)
|
||||
}
|
||||
|
||||
func (b *retryableRequestBody) Seek(offset int64, whence int) (offsetFromStart int64, err error) {
|
||||
return b.body.Seek(offset, whence)
|
||||
}
|
||||
|
||||
func (b *retryableRequestBody) Close() error {
|
||||
// We don't want the underlying transport to close the request body on transient failures so this is a nop.
|
||||
// The pipeline closes the request body upon success.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *retryableRequestBody) realClose() error {
|
||||
if c, ok := b.body.(io.Closer); ok {
|
||||
return c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package pipeline
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The Response interface exposes an http.Response object as it returns through the pipeline of Policy objects.
|
||||
// This ensures that Policy objects have access to the HTTP response. However, the object this interface encapsulates
|
||||
// might be a struct with additional fields that is created by a Policy object (typically a method-specific Factory).
|
||||
// The method that injected the method-specific Factory gets this returned Response and performs a type assertion
|
||||
// to the expected struct and returns the struct to its caller.
|
||||
type Response interface {
|
||||
Response() *http.Response
|
||||
}
|
||||
|
||||
// This is the default struct that has the http.Response.
|
||||
// A method can replace this struct with its own struct containing an http.Response
|
||||
// field and any other additional fields.
|
||||
type httpResponse struct {
|
||||
response *http.Response
|
||||
}
|
||||
|
||||
// NewHTTPResponse is typically called by a Policy object to return a Response object.
|
||||
func NewHTTPResponse(response *http.Response) Response {
|
||||
return &httpResponse{response: response}
|
||||
}
|
||||
|
||||
// This method satisfies the public Response interface's Response method
|
||||
func (r httpResponse) Response() *http.Response {
|
||||
return r.response
|
||||
}
|
||||
|
||||
// WriteRequestWithResponse appends a formatted HTTP request into a Buffer. If request and/or err are
|
||||
// not nil, then these are also written into the Buffer.
|
||||
func WriteRequestWithResponse(b *bytes.Buffer, request *http.Request, response *http.Response, err error) {
|
||||
// Write the request into the buffer.
|
||||
fmt.Fprint(b, " "+request.Method+" "+request.URL.String()+"\n")
|
||||
writeHeader(b, request.Header)
|
||||
if response != nil {
|
||||
fmt.Fprintln(b, " --------------------------------------------------------------------------------")
|
||||
fmt.Fprint(b, " RESPONSE Status: "+response.Status+"\n")
|
||||
writeHeader(b, response.Header)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintln(b, " --------------------------------------------------------------------------------")
|
||||
fmt.Fprint(b, " ERROR:\n"+err.Error()+"\n")
|
||||
}
|
||||
}
|
||||
|
||||
// formatHeaders appends an HTTP request's or response's header into a Buffer.
|
||||
func writeHeader(b *bytes.Buffer, header map[string][]string) {
|
||||
if len(header) == 0 {
|
||||
b.WriteString(" (no headers)\n")
|
||||
return
|
||||
}
|
||||
keys := make([]string, 0, len(header))
|
||||
// Alphabetize the headers
|
||||
for k := range header {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
// Redact the value of any Authorization header to prevent security information from persisting in logs
|
||||
value := interface{}("REDACTED")
|
||||
if !strings.EqualFold(k, "Authorization") {
|
||||
value = header[k]
|
||||
}
|
||||
fmt.Fprintf(b, " %s: %+v\n", k, value)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package pipeline
|
||||
|
||||
const (
|
||||
// UserAgent is the string to be used in the user agent string when making requests.
|
||||
UserAgent = "azure-pipeline-go/" + Version
|
||||
|
||||
// Version is the semantic version (see http://semver.org) of the pipeline package.
|
||||
Version = "0.2.1"
|
||||
)
|
|
@ -1,22 +0,0 @@
|
|||
# Azure Storage SDK for Go (Preview)
|
||||
|
||||
:exclamation: IMPORTANT: This package is in maintenance only and will be deprecated in the
|
||||
future. Please use one of the following packages instead.
|
||||
|
||||
| Service | Import Path/Repo |
|
||||
|---------|------------------|
|
||||
| Storage - Blobs | [github.com/Azure/azure-storage-blob-go](https://github.com/Azure/azure-storage-blob-go) |
|
||||
| Storage - Files | [github.com/Azure/azure-storage-file-go](https://github.com/Azure/azure-storage-file-go) |
|
||||
| Storage - Queues | [github.com/Azure/azure-storage-queue-go](https://github.com/Azure/azure-storage-queue-go) |
|
||||
|
||||
The `github.com/Azure/azure-sdk-for-go/storage` package is used to manage
|
||||
[Azure Storage](https://docs.microsoft.com/en-us/azure/storage/) data plane
|
||||
resources: containers, blobs, tables, and queues.
|
||||
|
||||
To manage storage *accounts* use Azure Resource Manager (ARM) via the packages
|
||||
at [github.com/Azure/azure-sdk-for-go/services/storage](https://github.com/Azure/azure-sdk-for-go/tree/master/services/storage).
|
||||
|
||||
This package also supports the [Azure Storage
|
||||
Emulator](https://azure.microsoft.com/documentation/articles/storage-use-emulator/)
|
||||
(Windows only).
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
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 (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PutAppendBlob initializes an empty append blob with specified name. An
|
||||
// append blob must be created using this method before appending 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) PutAppendBlob(options *PutBlobOptions) error {
|
||||
params := url.Values{}
|
||||
headers := b.Container.bsc.client.getStandardHeaders()
|
||||
headers["x-ms-blob-type"] = string(BlobTypeAppend)
|
||||
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, nil, b.Container.bsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.respondCreation(resp, BlobTypeAppend)
|
||||
}
|
||||
|
||||
// AppendBlockOptions includes the options for an append block operation
|
||||
type AppendBlockOptions struct {
|
||||
Timeout uint
|
||||
LeaseID string `header:"x-ms-lease-id"`
|
||||
MaxSize *uint `header:"x-ms-blob-condition-maxsize"`
|
||||
AppendPosition *uint `header:"x-ms-blob-condition-appendpos"`
|
||||
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"`
|
||||
ContentMD5 bool
|
||||
}
|
||||
|
||||
// AppendBlock appends a block to an append blob.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Append-Block
|
||||
func (b *Blob) AppendBlock(chunk []byte, options *AppendBlockOptions) error {
|
||||
params := url.Values{"comp": {"appendblock"}}
|
||||
headers := b.Container.bsc.client.getStandardHeaders()
|
||||
headers["x-ms-blob-type"] = string(BlobTypeAppend)
|
||||
headers["Content-Length"] = fmt.Sprintf("%v", len(chunk))
|
||||
|
||||
if options != nil {
|
||||
params = addTimeout(params, options.Timeout)
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
if options.ContentMD5 {
|
||||
md5sum := md5.Sum(chunk)
|
||||
headers[headerContentMD5] = base64.StdEncoding.EncodeToString(md5sum[:])
|
||||
}
|
||||
}
|
||||
uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
|
||||
|
||||
resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, bytes.NewReader(chunk), b.Container.bsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.respondCreation(resp, BlobTypeAppend)
|
||||
}
|
|
@ -1,246 +0,0 @@
|
|||
// Package storage provides clients for Microsoft Azure Storage Services.
|
||||
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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// See: https://docs.microsoft.com/rest/api/storageservices/fileservices/authentication-for-the-azure-storage-services
|
||||
|
||||
type authentication string
|
||||
|
||||
const (
|
||||
sharedKey authentication = "sharedKey"
|
||||
sharedKeyForTable authentication = "sharedKeyTable"
|
||||
sharedKeyLite authentication = "sharedKeyLite"
|
||||
sharedKeyLiteForTable authentication = "sharedKeyLiteTable"
|
||||
|
||||
// headers
|
||||
headerAcceptCharset = "Accept-Charset"
|
||||
headerAuthorization = "Authorization"
|
||||
headerContentLength = "Content-Length"
|
||||
headerDate = "Date"
|
||||
headerXmsDate = "x-ms-date"
|
||||
headerXmsVersion = "x-ms-version"
|
||||
headerContentEncoding = "Content-Encoding"
|
||||
headerContentLanguage = "Content-Language"
|
||||
headerContentType = "Content-Type"
|
||||
headerContentMD5 = "Content-MD5"
|
||||
headerIfModifiedSince = "If-Modified-Since"
|
||||
headerIfMatch = "If-Match"
|
||||
headerIfNoneMatch = "If-None-Match"
|
||||
headerIfUnmodifiedSince = "If-Unmodified-Since"
|
||||
headerRange = "Range"
|
||||
headerDataServiceVersion = "DataServiceVersion"
|
||||
headerMaxDataServiceVersion = "MaxDataServiceVersion"
|
||||
headerContentTransferEncoding = "Content-Transfer-Encoding"
|
||||
)
|
||||
|
||||
func (c *Client) addAuthorizationHeader(verb, url string, headers map[string]string, auth authentication) (map[string]string, error) {
|
||||
if !c.sasClient {
|
||||
authHeader, err := c.getSharedKey(verb, url, headers, auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headers[headerAuthorization] = authHeader
|
||||
}
|
||||
return headers, nil
|
||||
}
|
||||
|
||||
func (c *Client) getSharedKey(verb, url string, headers map[string]string, auth authentication) (string, error) {
|
||||
canRes, err := c.buildCanonicalizedResource(url, auth, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
canString, err := buildCanonicalizedString(verb, headers, canRes, auth)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return c.createAuthorizationHeader(canString, auth), nil
|
||||
}
|
||||
|
||||
func (c *Client) buildCanonicalizedResource(uri string, auth authentication, sas bool) (string, error) {
|
||||
errMsg := "buildCanonicalizedResource error: %s"
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(errMsg, err.Error())
|
||||
}
|
||||
|
||||
cr := bytes.NewBufferString("")
|
||||
if c.accountName != StorageEmulatorAccountName || !sas {
|
||||
cr.WriteString("/")
|
||||
cr.WriteString(c.getCanonicalizedAccountName())
|
||||
}
|
||||
|
||||
if len(u.Path) > 0 {
|
||||
// Any portion of the CanonicalizedResource string that is derived from
|
||||
// the resource's URI should be encoded exactly as it is in the URI.
|
||||
// -- https://msdn.microsoft.com/en-gb/library/azure/dd179428.aspx
|
||||
cr.WriteString(u.EscapedPath())
|
||||
}
|
||||
|
||||
params, err := url.ParseQuery(u.RawQuery)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(errMsg, err.Error())
|
||||
}
|
||||
|
||||
// See https://github.com/Azure/azure-storage-net/blob/master/Lib/Common/Core/Util/AuthenticationUtility.cs#L277
|
||||
if auth == sharedKey {
|
||||
if len(params) > 0 {
|
||||
cr.WriteString("\n")
|
||||
|
||||
keys := []string{}
|
||||
for key := range params {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
completeParams := []string{}
|
||||
for _, key := range keys {
|
||||
if len(params[key]) > 1 {
|
||||
sort.Strings(params[key])
|
||||
}
|
||||
|
||||
completeParams = append(completeParams, fmt.Sprintf("%s:%s", key, strings.Join(params[key], ",")))
|
||||
}
|
||||
cr.WriteString(strings.Join(completeParams, "\n"))
|
||||
}
|
||||
} else {
|
||||
// search for "comp" parameter, if exists then add it to canonicalizedresource
|
||||
if v, ok := params["comp"]; ok {
|
||||
cr.WriteString("?comp=" + v[0])
|
||||
}
|
||||
}
|
||||
|
||||
return string(cr.Bytes()), nil
|
||||
}
|
||||
|
||||
func (c *Client) getCanonicalizedAccountName() string {
|
||||
// since we may be trying to access a secondary storage account, we need to
|
||||
// remove the -secondary part of the storage name
|
||||
return strings.TrimSuffix(c.accountName, "-secondary")
|
||||
}
|
||||
|
||||
func buildCanonicalizedString(verb string, headers map[string]string, canonicalizedResource string, auth authentication) (string, error) {
|
||||
contentLength := headers[headerContentLength]
|
||||
if contentLength == "0" {
|
||||
contentLength = ""
|
||||
}
|
||||
date := headers[headerDate]
|
||||
if v, ok := headers[headerXmsDate]; ok {
|
||||
if auth == sharedKey || auth == sharedKeyLite {
|
||||
date = ""
|
||||
} else {
|
||||
date = v
|
||||
}
|
||||
}
|
||||
var canString string
|
||||
switch auth {
|
||||
case sharedKey:
|
||||
canString = strings.Join([]string{
|
||||
verb,
|
||||
headers[headerContentEncoding],
|
||||
headers[headerContentLanguage],
|
||||
contentLength,
|
||||
headers[headerContentMD5],
|
||||
headers[headerContentType],
|
||||
date,
|
||||
headers[headerIfModifiedSince],
|
||||
headers[headerIfMatch],
|
||||
headers[headerIfNoneMatch],
|
||||
headers[headerIfUnmodifiedSince],
|
||||
headers[headerRange],
|
||||
buildCanonicalizedHeader(headers),
|
||||
canonicalizedResource,
|
||||
}, "\n")
|
||||
case sharedKeyForTable:
|
||||
canString = strings.Join([]string{
|
||||
verb,
|
||||
headers[headerContentMD5],
|
||||
headers[headerContentType],
|
||||
date,
|
||||
canonicalizedResource,
|
||||
}, "\n")
|
||||
case sharedKeyLite:
|
||||
canString = strings.Join([]string{
|
||||
verb,
|
||||
headers[headerContentMD5],
|
||||
headers[headerContentType],
|
||||
date,
|
||||
buildCanonicalizedHeader(headers),
|
||||
canonicalizedResource,
|
||||
}, "\n")
|
||||
case sharedKeyLiteForTable:
|
||||
canString = strings.Join([]string{
|
||||
date,
|
||||
canonicalizedResource,
|
||||
}, "\n")
|
||||
default:
|
||||
return "", fmt.Errorf("%s authentication is not supported yet", auth)
|
||||
}
|
||||
return canString, nil
|
||||
}
|
||||
|
||||
func buildCanonicalizedHeader(headers map[string]string) string {
|
||||
cm := make(map[string]string)
|
||||
|
||||
for k, v := range headers {
|
||||
headerName := strings.TrimSpace(strings.ToLower(k))
|
||||
if strings.HasPrefix(headerName, "x-ms-") {
|
||||
cm[headerName] = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(cm) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
keys := []string{}
|
||||
for key := range cm {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
ch := bytes.NewBufferString("")
|
||||
|
||||
for _, key := range keys {
|
||||
ch.WriteString(key)
|
||||
ch.WriteRune(':')
|
||||
ch.WriteString(cm[key])
|
||||
ch.WriteRune('\n')
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(string(ch.Bytes()), "\n")
|
||||
}
|
||||
|
||||
func (c *Client) createAuthorizationHeader(canonicalizedString string, auth authentication) string {
|
||||
signature := c.computeHmac256(canonicalizedString)
|
||||
var key string
|
||||
switch auth {
|
||||
case sharedKey, sharedKeyForTable:
|
||||
key = "SharedKey"
|
||||
case sharedKeyLite, sharedKeyLiteForTable:
|
||||
key = "SharedKeyLite"
|
||||
}
|
||||
return fmt.Sprintf("%s %s:%s", key, c.getCanonicalizedAccountName(), signature)
|
||||
}
|
|
@ -1,632 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -1,179 +0,0 @@
|
|||
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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// OverrideHeaders defines overridable response heaedrs in
|
||||
// a request using a SAS URI.
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas
|
||||
type OverrideHeaders struct {
|
||||
CacheControl string
|
||||
ContentDisposition string
|
||||
ContentEncoding string
|
||||
ContentLanguage string
|
||||
ContentType string
|
||||
}
|
||||
|
||||
// BlobSASOptions are options to construct a blob SAS
|
||||
// URI.
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas
|
||||
type BlobSASOptions struct {
|
||||
BlobServiceSASPermissions
|
||||
OverrideHeaders
|
||||
SASOptions
|
||||
}
|
||||
|
||||
// BlobServiceSASPermissions includes the available permissions for
|
||||
// blob service SAS URI.
|
||||
type BlobServiceSASPermissions struct {
|
||||
Read bool
|
||||
Add bool
|
||||
Create bool
|
||||
Write bool
|
||||
Delete bool
|
||||
}
|
||||
|
||||
func (p BlobServiceSASPermissions) buildString() string {
|
||||
permissions := ""
|
||||
if p.Read {
|
||||
permissions += "r"
|
||||
}
|
||||
if p.Add {
|
||||
permissions += "a"
|
||||
}
|
||||
if p.Create {
|
||||
permissions += "c"
|
||||
}
|
||||
if p.Write {
|
||||
permissions += "w"
|
||||
}
|
||||
if p.Delete {
|
||||
permissions += "d"
|
||||
}
|
||||
return permissions
|
||||
}
|
||||
|
||||
// GetSASURI creates an URL to the blob which contains the Shared
|
||||
// Access Signature with the specified options.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas
|
||||
func (b *Blob) GetSASURI(options BlobSASOptions) (string, error) {
|
||||
uri := b.GetURL()
|
||||
signedResource := "b"
|
||||
canonicalizedResource, err := b.Container.bsc.client.buildCanonicalizedResource(uri, b.Container.bsc.auth, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
permissions := options.BlobServiceSASPermissions.buildString()
|
||||
return b.Container.bsc.client.blobAndFileSASURI(options.SASOptions, uri, permissions, canonicalizedResource, signedResource, options.OverrideHeaders)
|
||||
}
|
||||
|
||||
func (c *Client) blobAndFileSASURI(options SASOptions, uri, permissions, canonicalizedResource, signedResource string, headers OverrideHeaders) (string, error) {
|
||||
start := ""
|
||||
if options.Start != (time.Time{}) {
|
||||
start = options.Start.UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
expiry := options.Expiry.UTC().Format(time.RFC3339)
|
||||
|
||||
// We need to replace + with %2b first to avoid being treated as a space (which is correct for query strings, but not the path component).
|
||||
canonicalizedResource = strings.Replace(canonicalizedResource, "+", "%2b", -1)
|
||||
canonicalizedResource, err := url.QueryUnescape(canonicalizedResource)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
protocols := ""
|
||||
if options.UseHTTPS {
|
||||
protocols = "https"
|
||||
}
|
||||
stringToSign, err := blobSASStringToSign(permissions, start, expiry, canonicalizedResource, options.Identifier, options.IP, protocols, c.apiVersion, signedResource, "", headers)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
sig := c.computeHmac256(stringToSign)
|
||||
sasParams := url.Values{
|
||||
"sv": {c.apiVersion},
|
||||
"se": {expiry},
|
||||
"sr": {signedResource},
|
||||
"sp": {permissions},
|
||||
"sig": {sig},
|
||||
}
|
||||
|
||||
if start != "" {
|
||||
sasParams.Add("st", start)
|
||||
}
|
||||
|
||||
if c.apiVersion >= "2015-04-05" {
|
||||
if protocols != "" {
|
||||
sasParams.Add("spr", protocols)
|
||||
}
|
||||
if options.IP != "" {
|
||||
sasParams.Add("sip", options.IP)
|
||||
}
|
||||
}
|
||||
|
||||
// Add override response hedaers
|
||||
addQueryParameter(sasParams, "rscc", headers.CacheControl)
|
||||
addQueryParameter(sasParams, "rscd", headers.ContentDisposition)
|
||||
addQueryParameter(sasParams, "rsce", headers.ContentEncoding)
|
||||
addQueryParameter(sasParams, "rscl", headers.ContentLanguage)
|
||||
addQueryParameter(sasParams, "rsct", headers.ContentType)
|
||||
|
||||
sasURL, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sasURL.RawQuery = sasParams.Encode()
|
||||
return sasURL.String(), nil
|
||||
}
|
||||
|
||||
func blobSASStringToSign(signedPermissions, signedStart, signedExpiry, canonicalizedResource, signedIdentifier, signedIP, protocols, signedVersion, signedResource, signedSnapshotTime string, headers OverrideHeaders) (string, error) {
|
||||
rscc := headers.CacheControl
|
||||
rscd := headers.ContentDisposition
|
||||
rsce := headers.ContentEncoding
|
||||
rscl := headers.ContentLanguage
|
||||
rsct := headers.ContentType
|
||||
|
||||
if signedVersion >= "2015-02-21" {
|
||||
canonicalizedResource = "/blob" + canonicalizedResource
|
||||
}
|
||||
|
||||
// https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas
|
||||
if signedVersion >= "2018-11-09" {
|
||||
return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s", signedPermissions, signedStart, signedExpiry, canonicalizedResource, signedIdentifier, signedIP, protocols, signedVersion, signedResource, signedSnapshotTime, rscc, rscd, rsce, rscl, rsct), nil
|
||||
}
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/azure/dn140255.aspx#Anchor_12
|
||||
if signedVersion >= "2015-04-05" {
|
||||
return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s", signedPermissions, signedStart, signedExpiry, canonicalizedResource, signedIdentifier, signedIP, protocols, signedVersion, rscc, rscd, rsce, rscl, rsct), nil
|
||||
}
|
||||
|
||||
// reference: http://msdn.microsoft.com/en-us/library/azure/dn140255.aspx
|
||||
if signedVersion >= "2013-08-15" {
|
||||
return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s", signedPermissions, signedStart, signedExpiry, canonicalizedResource, signedIdentifier, signedVersion, rscc, rscd, rsce, rscl, rsct), nil
|
||||
}
|
||||
|
||||
return "", errors.New("storage: not implemented SAS for versions earlier than 2013-08-15")
|
||||
}
|
|
@ -1,186 +0,0 @@
|
|||
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"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BlobStorageClient contains operations for Microsoft Azure Blob Storage
|
||||
// Service.
|
||||
type BlobStorageClient struct {
|
||||
client Client
|
||||
auth authentication
|
||||
}
|
||||
|
||||
// GetServiceProperties gets the properties of your storage account's blob service.
|
||||
// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-blob-service-properties
|
||||
func (b *BlobStorageClient) GetServiceProperties() (*ServiceProperties, error) {
|
||||
return b.client.getServiceProperties(blobServiceName, b.auth)
|
||||
}
|
||||
|
||||
// SetServiceProperties sets the properties of your storage account's blob service.
|
||||
// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/set-blob-service-properties
|
||||
func (b *BlobStorageClient) SetServiceProperties(props ServiceProperties) error {
|
||||
return b.client.setServiceProperties(props, blobServiceName, b.auth)
|
||||
}
|
||||
|
||||
// ListContainersParameters defines the set of customizable parameters to make a
|
||||
// List Containers call.
|
||||
//
|
||||
// See https://msdn.microsoft.com/en-us/library/azure/dd179352.aspx
|
||||
type ListContainersParameters struct {
|
||||
Prefix string
|
||||
Marker string
|
||||
Include string
|
||||
MaxResults uint
|
||||
Timeout uint
|
||||
}
|
||||
|
||||
// GetContainerReference returns a Container object for the specified container name.
|
||||
func (b *BlobStorageClient) GetContainerReference(name string) *Container {
|
||||
return &Container{
|
||||
bsc: b,
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// GetContainerReferenceFromSASURI returns a Container object for the specified
|
||||
// container SASURI
|
||||
func GetContainerReferenceFromSASURI(sasuri url.URL) (*Container, error) {
|
||||
path := strings.Split(sasuri.Path, "/")
|
||||
if len(path) <= 1 {
|
||||
return nil, fmt.Errorf("could not find a container in URI: %s", sasuri.String())
|
||||
}
|
||||
c, err := newSASClientFromURL(&sasuri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cli := c.GetBlobService()
|
||||
return &Container{
|
||||
bsc: &cli,
|
||||
Name: path[1],
|
||||
sasuri: sasuri,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListContainers returns the list of containers in a storage account along with
|
||||
// pagination token and other response details.
|
||||
//
|
||||
// See https://msdn.microsoft.com/en-us/library/azure/dd179352.aspx
|
||||
func (b BlobStorageClient) ListContainers(params ListContainersParameters) (*ContainerListResponse, error) {
|
||||
q := mergeParams(params.getParameters(), url.Values{"comp": {"list"}})
|
||||
uri := b.client.getEndpoint(blobServiceName, "", q)
|
||||
headers := b.client.getStandardHeaders()
|
||||
|
||||
type ContainerAlias struct {
|
||||
bsc *BlobStorageClient
|
||||
Name string `xml:"Name"`
|
||||
Properties ContainerProperties `xml:"Properties"`
|
||||
Metadata BlobMetadata
|
||||
sasuri url.URL
|
||||
}
|
||||
type ContainerListResponseAlias struct {
|
||||
XMLName xml.Name `xml:"EnumerationResults"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Prefix string `xml:"Prefix"`
|
||||
Marker string `xml:"Marker"`
|
||||
NextMarker string `xml:"NextMarker"`
|
||||
MaxResults int64 `xml:"MaxResults"`
|
||||
Containers []ContainerAlias `xml:"Containers>Container"`
|
||||
}
|
||||
|
||||
var outAlias ContainerListResponseAlias
|
||||
resp, err := b.client.exec(http.MethodGet, uri, headers, nil, b.auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
err = xmlUnmarshal(resp.Body, &outAlias)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := ContainerListResponse{
|
||||
XMLName: outAlias.XMLName,
|
||||
Xmlns: outAlias.Xmlns,
|
||||
Prefix: outAlias.Prefix,
|
||||
Marker: outAlias.Marker,
|
||||
NextMarker: outAlias.NextMarker,
|
||||
MaxResults: outAlias.MaxResults,
|
||||
Containers: make([]Container, len(outAlias.Containers)),
|
||||
}
|
||||
for i, cnt := range outAlias.Containers {
|
||||
out.Containers[i] = Container{
|
||||
bsc: &b,
|
||||
Name: cnt.Name,
|
||||
Properties: cnt.Properties,
|
||||
Metadata: map[string]string(cnt.Metadata),
|
||||
sasuri: cnt.sasuri,
|
||||
}
|
||||
}
|
||||
|
||||
return &out, err
|
||||
}
|
||||
|
||||
func (p ListContainersParameters) getParameters() url.Values {
|
||||
out := url.Values{}
|
||||
|
||||
if p.Prefix != "" {
|
||||
out.Set("prefix", p.Prefix)
|
||||
}
|
||||
if p.Marker != "" {
|
||||
out.Set("marker", p.Marker)
|
||||
}
|
||||
if p.Include != "" {
|
||||
out.Set("include", p.Include)
|
||||
}
|
||||
if p.MaxResults != 0 {
|
||||
out.Set("maxresults", strconv.FormatUint(uint64(p.MaxResults), 10))
|
||||
}
|
||||
if p.Timeout != 0 {
|
||||
out.Set("timeout", strconv.FormatUint(uint64(p.Timeout), 10))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func writeMetadata(h http.Header) map[string]string {
|
||||
metadata := make(map[string]string)
|
||||
for k, v := range h {
|
||||
// Can't trust CanonicalHeaderKey() to munge case
|
||||
// reliably. "_" is allowed in identifiers:
|
||||
// https://msdn.microsoft.com/en-us/library/azure/dd179414.aspx
|
||||
// https://msdn.microsoft.com/library/aa664670(VS.71).aspx
|
||||
// http://tools.ietf.org/html/rfc7230#section-3.2
|
||||
// ...but "_" is considered invalid by
|
||||
// CanonicalMIMEHeaderKey in
|
||||
// https://golang.org/src/net/textproto/reader.go?s=14615:14659#L542
|
||||
// so k can be "X-Ms-Meta-Lol" or "x-ms-meta-lol_rofl".
|
||||
k = strings.ToLower(k)
|
||||
if len(v) == 0 || !strings.HasPrefix(k, strings.ToLower(userDefinedMetadataHeaderPrefix)) {
|
||||
continue
|
||||
}
|
||||
// metadata["lol"] = content of the last X-Ms-Meta-Lol header
|
||||
k = k[len(userDefinedMetadataHeaderPrefix):]
|
||||
metadata[k] = v[len(v)-1]
|
||||
}
|
||||
return metadata
|
||||
}
|
|
@ -1,311 +0,0 @@
|
|||
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 (
|
||||
"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
|
||||
}
|
|
@ -1,991 +0,0 @@
|
|||
// Package storage provides clients for Microsoft Azure Storage Services.
|
||||
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 (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/version"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultBaseURL is the domain name used for storage requests in the
|
||||
// public cloud when a default client is created.
|
||||
DefaultBaseURL = "core.windows.net"
|
||||
|
||||
// DefaultAPIVersion is the Azure Storage API version string used when a
|
||||
// basic client is created.
|
||||
DefaultAPIVersion = "2018-03-28"
|
||||
|
||||
defaultUseHTTPS = true
|
||||
defaultRetryAttempts = 5
|
||||
defaultRetryDuration = time.Second * 5
|
||||
|
||||
// StorageEmulatorAccountName is the fixed storage account used by Azure Storage Emulator
|
||||
StorageEmulatorAccountName = "devstoreaccount1"
|
||||
|
||||
// StorageEmulatorAccountKey is the the fixed storage account used by Azure Storage Emulator
|
||||
StorageEmulatorAccountKey = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
|
||||
|
||||
blobServiceName = "blob"
|
||||
tableServiceName = "table"
|
||||
queueServiceName = "queue"
|
||||
fileServiceName = "file"
|
||||
|
||||
storageEmulatorBlob = "127.0.0.1:10000"
|
||||
storageEmulatorTable = "127.0.0.1:10002"
|
||||
storageEmulatorQueue = "127.0.0.1:10001"
|
||||
|
||||
userAgentHeader = "User-Agent"
|
||||
|
||||
userDefinedMetadataHeaderPrefix = "x-ms-meta-"
|
||||
|
||||
connectionStringAccountName = "accountname"
|
||||
connectionStringAccountKey = "accountkey"
|
||||
connectionStringEndpointSuffix = "endpointsuffix"
|
||||
connectionStringEndpointProtocol = "defaultendpointsprotocol"
|
||||
|
||||
connectionStringBlobEndpoint = "blobendpoint"
|
||||
connectionStringFileEndpoint = "fileendpoint"
|
||||
connectionStringQueueEndpoint = "queueendpoint"
|
||||
connectionStringTableEndpoint = "tableendpoint"
|
||||
connectionStringSAS = "sharedaccesssignature"
|
||||
)
|
||||
|
||||
var (
|
||||
validStorageAccount = regexp.MustCompile("^[0-9a-z]{3,24}$")
|
||||
defaultValidStatusCodes = []int{
|
||||
http.StatusRequestTimeout, // 408
|
||||
http.StatusInternalServerError, // 500
|
||||
http.StatusBadGateway, // 502
|
||||
http.StatusServiceUnavailable, // 503
|
||||
http.StatusGatewayTimeout, // 504
|
||||
}
|
||||
)
|
||||
|
||||
// Sender sends a request
|
||||
type Sender interface {
|
||||
Send(*Client, *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// DefaultSender is the default sender for the client. It implements
|
||||
// an automatic retry strategy.
|
||||
type DefaultSender struct {
|
||||
RetryAttempts int
|
||||
RetryDuration time.Duration
|
||||
ValidStatusCodes []int
|
||||
attempts int // used for testing
|
||||
}
|
||||
|
||||
// Send is the default retry strategy in the client
|
||||
func (ds *DefaultSender) Send(c *Client, req *http.Request) (resp *http.Response, err error) {
|
||||
rr := autorest.NewRetriableRequest(req)
|
||||
for attempts := 0; attempts < ds.RetryAttempts; attempts++ {
|
||||
err = rr.Prepare()
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
resp, err = c.HTTPClient.Do(rr.Request())
|
||||
if err != nil || !autorest.ResponseHasStatusCode(resp, ds.ValidStatusCodes...) {
|
||||
return resp, err
|
||||
}
|
||||
drainRespBody(resp)
|
||||
autorest.DelayForBackoff(ds.RetryDuration, attempts, req.Cancel)
|
||||
ds.attempts = attempts
|
||||
}
|
||||
ds.attempts++
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Client is the object that needs to be constructed to perform
|
||||
// operations on the storage account.
|
||||
type Client struct {
|
||||
// HTTPClient is the http.Client used to initiate API
|
||||
// requests. http.DefaultClient is used when creating a
|
||||
// client.
|
||||
HTTPClient *http.Client
|
||||
|
||||
// Sender is an interface that sends the request. Clients are
|
||||
// created with a DefaultSender. The DefaultSender has an
|
||||
// automatic retry strategy built in. The Sender can be customized.
|
||||
Sender Sender
|
||||
|
||||
accountName string
|
||||
accountKey []byte
|
||||
useHTTPS bool
|
||||
UseSharedKeyLite bool
|
||||
baseURL string
|
||||
apiVersion string
|
||||
userAgent string
|
||||
sasClient bool
|
||||
accountSASToken url.Values
|
||||
}
|
||||
|
||||
type odataResponse struct {
|
||||
resp *http.Response
|
||||
odata odataErrorWrapper
|
||||
}
|
||||
|
||||
// AzureStorageServiceError contains fields of the error response from
|
||||
// Azure Storage Service REST API. See https://msdn.microsoft.com/en-us/library/azure/dd179382.aspx
|
||||
// Some fields might be specific to certain calls.
|
||||
type AzureStorageServiceError struct {
|
||||
Code string `xml:"Code"`
|
||||
Message string `xml:"Message"`
|
||||
AuthenticationErrorDetail string `xml:"AuthenticationErrorDetail"`
|
||||
QueryParameterName string `xml:"QueryParameterName"`
|
||||
QueryParameterValue string `xml:"QueryParameterValue"`
|
||||
Reason string `xml:"Reason"`
|
||||
Lang string
|
||||
StatusCode int
|
||||
RequestID string
|
||||
Date string
|
||||
APIVersion string
|
||||
}
|
||||
|
||||
type odataErrorMessage struct {
|
||||
Lang string `json:"lang"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type odataError struct {
|
||||
Code string `json:"code"`
|
||||
Message odataErrorMessage `json:"message"`
|
||||
}
|
||||
|
||||
type odataErrorWrapper struct {
|
||||
Err odataError `json:"odata.error"`
|
||||
}
|
||||
|
||||
// UnexpectedStatusCodeError is returned when a storage service responds with neither an error
|
||||
// nor with an HTTP status code indicating success.
|
||||
type UnexpectedStatusCodeError struct {
|
||||
allowed []int
|
||||
got int
|
||||
inner error
|
||||
}
|
||||
|
||||
func (e UnexpectedStatusCodeError) Error() string {
|
||||
s := func(i int) string { return fmt.Sprintf("%d %s", i, http.StatusText(i)) }
|
||||
|
||||
got := s(e.got)
|
||||
expected := []string{}
|
||||
for _, v := range e.allowed {
|
||||
expected = append(expected, s(v))
|
||||
}
|
||||
return fmt.Sprintf("storage: status code from service response is %s; was expecting %s. Inner error: %+v", got, strings.Join(expected, " or "), e.inner)
|
||||
}
|
||||
|
||||
// Got is the actual status code returned by Azure.
|
||||
func (e UnexpectedStatusCodeError) Got() int {
|
||||
return e.got
|
||||
}
|
||||
|
||||
// Inner returns any inner error info.
|
||||
func (e UnexpectedStatusCodeError) Inner() error {
|
||||
return e.inner
|
||||
}
|
||||
|
||||
// NewClientFromConnectionString creates a Client from the connection string.
|
||||
func NewClientFromConnectionString(input string) (Client, error) {
|
||||
// build a map of connection string key/value pairs
|
||||
parts := map[string]string{}
|
||||
for _, pair := range strings.Split(input, ";") {
|
||||
if pair == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
equalDex := strings.IndexByte(pair, '=')
|
||||
if equalDex <= 0 {
|
||||
return Client{}, fmt.Errorf("Invalid connection segment %q", pair)
|
||||
}
|
||||
|
||||
value := strings.TrimSpace(pair[equalDex+1:])
|
||||
key := strings.TrimSpace(strings.ToLower(pair[:equalDex]))
|
||||
parts[key] = value
|
||||
}
|
||||
|
||||
// TODO: validate parameter sets?
|
||||
|
||||
if parts[connectionStringAccountName] == StorageEmulatorAccountName {
|
||||
return NewEmulatorClient()
|
||||
}
|
||||
|
||||
if parts[connectionStringSAS] != "" {
|
||||
endpoint := ""
|
||||
if parts[connectionStringBlobEndpoint] != "" {
|
||||
endpoint = parts[connectionStringBlobEndpoint]
|
||||
} else if parts[connectionStringFileEndpoint] != "" {
|
||||
endpoint = parts[connectionStringFileEndpoint]
|
||||
} else if parts[connectionStringQueueEndpoint] != "" {
|
||||
endpoint = parts[connectionStringQueueEndpoint]
|
||||
} else {
|
||||
endpoint = parts[connectionStringTableEndpoint]
|
||||
}
|
||||
|
||||
return NewAccountSASClientFromEndpointToken(endpoint, parts[connectionStringSAS])
|
||||
}
|
||||
|
||||
useHTTPS := defaultUseHTTPS
|
||||
if parts[connectionStringEndpointProtocol] != "" {
|
||||
useHTTPS = parts[connectionStringEndpointProtocol] == "https"
|
||||
}
|
||||
|
||||
return NewClient(parts[connectionStringAccountName], parts[connectionStringAccountKey],
|
||||
parts[connectionStringEndpointSuffix], DefaultAPIVersion, useHTTPS)
|
||||
}
|
||||
|
||||
// NewBasicClient constructs a Client with given storage service name and
|
||||
// key.
|
||||
func NewBasicClient(accountName, accountKey string) (Client, error) {
|
||||
if accountName == StorageEmulatorAccountName {
|
||||
return NewEmulatorClient()
|
||||
}
|
||||
return NewClient(accountName, accountKey, DefaultBaseURL, DefaultAPIVersion, defaultUseHTTPS)
|
||||
}
|
||||
|
||||
// NewBasicClientOnSovereignCloud constructs a Client with given storage service name and
|
||||
// key in the referenced cloud.
|
||||
func NewBasicClientOnSovereignCloud(accountName, accountKey string, env azure.Environment) (Client, error) {
|
||||
if accountName == StorageEmulatorAccountName {
|
||||
return NewEmulatorClient()
|
||||
}
|
||||
return NewClient(accountName, accountKey, env.StorageEndpointSuffix, DefaultAPIVersion, defaultUseHTTPS)
|
||||
}
|
||||
|
||||
//NewEmulatorClient contructs a Client intended to only work with Azure
|
||||
//Storage Emulator
|
||||
func NewEmulatorClient() (Client, error) {
|
||||
return NewClient(StorageEmulatorAccountName, StorageEmulatorAccountKey, DefaultBaseURL, DefaultAPIVersion, false)
|
||||
}
|
||||
|
||||
// NewClient constructs a Client. This should be used if the caller wants
|
||||
// to specify whether to use HTTPS, a specific REST API version or a custom
|
||||
// storage endpoint than Azure Public Cloud.
|
||||
func NewClient(accountName, accountKey, serviceBaseURL, apiVersion string, useHTTPS bool) (Client, error) {
|
||||
var c Client
|
||||
if !IsValidStorageAccount(accountName) {
|
||||
return c, fmt.Errorf("azure: account name is not valid: it must be between 3 and 24 characters, and only may contain numbers and lowercase letters: %v", accountName)
|
||||
} else if accountKey == "" {
|
||||
return c, fmt.Errorf("azure: account key required")
|
||||
} else if serviceBaseURL == "" {
|
||||
return c, fmt.Errorf("azure: base storage service url required")
|
||||
}
|
||||
|
||||
key, err := base64.StdEncoding.DecodeString(accountKey)
|
||||
if err != nil {
|
||||
return c, fmt.Errorf("azure: malformed storage account key: %v", err)
|
||||
}
|
||||
|
||||
c = Client{
|
||||
HTTPClient: http.DefaultClient,
|
||||
accountName: accountName,
|
||||
accountKey: key,
|
||||
useHTTPS: useHTTPS,
|
||||
baseURL: serviceBaseURL,
|
||||
apiVersion: apiVersion,
|
||||
sasClient: false,
|
||||
UseSharedKeyLite: false,
|
||||
Sender: &DefaultSender{
|
||||
RetryAttempts: defaultRetryAttempts,
|
||||
ValidStatusCodes: defaultValidStatusCodes,
|
||||
RetryDuration: defaultRetryDuration,
|
||||
},
|
||||
}
|
||||
c.userAgent = c.getDefaultUserAgent()
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// IsValidStorageAccount checks if the storage account name is valid.
|
||||
// See https://docs.microsoft.com/en-us/azure/storage/storage-create-storage-account
|
||||
func IsValidStorageAccount(account string) bool {
|
||||
return validStorageAccount.MatchString(account)
|
||||
}
|
||||
|
||||
// NewAccountSASClient contructs a client that uses accountSAS authorization
|
||||
// for its operations.
|
||||
func NewAccountSASClient(account string, token url.Values, env azure.Environment) Client {
|
||||
return newSASClient(account, env.StorageEndpointSuffix, token)
|
||||
}
|
||||
|
||||
// NewAccountSASClientFromEndpointToken constructs a client that uses accountSAS authorization
|
||||
// for its operations using the specified endpoint and SAS token.
|
||||
func NewAccountSASClientFromEndpointToken(endpoint string, sasToken string) (Client, error) {
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return Client{}, err
|
||||
}
|
||||
_, err = url.ParseQuery(sasToken)
|
||||
if err != nil {
|
||||
return Client{}, err
|
||||
}
|
||||
u.RawQuery = sasToken
|
||||
return newSASClientFromURL(u)
|
||||
}
|
||||
|
||||
func newSASClient(accountName, baseURL string, sasToken url.Values) Client {
|
||||
c := Client{
|
||||
HTTPClient: http.DefaultClient,
|
||||
apiVersion: DefaultAPIVersion,
|
||||
sasClient: true,
|
||||
Sender: &DefaultSender{
|
||||
RetryAttempts: defaultRetryAttempts,
|
||||
ValidStatusCodes: defaultValidStatusCodes,
|
||||
RetryDuration: defaultRetryDuration,
|
||||
},
|
||||
accountName: accountName,
|
||||
baseURL: baseURL,
|
||||
accountSASToken: sasToken,
|
||||
useHTTPS: defaultUseHTTPS,
|
||||
}
|
||||
c.userAgent = c.getDefaultUserAgent()
|
||||
// Get API version and protocol from token
|
||||
c.apiVersion = sasToken.Get("sv")
|
||||
if spr := sasToken.Get("spr"); spr != "" {
|
||||
c.useHTTPS = spr == "https"
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func newSASClientFromURL(u *url.URL) (Client, error) {
|
||||
// the host name will look something like this
|
||||
// - foo.blob.core.windows.net
|
||||
// "foo" is the account name
|
||||
// "core.windows.net" is the baseURL
|
||||
|
||||
// find the first dot to get account name
|
||||
i1 := strings.IndexByte(u.Host, '.')
|
||||
if i1 < 0 {
|
||||
return Client{}, fmt.Errorf("failed to find '.' in %s", u.Host)
|
||||
}
|
||||
|
||||
// now find the second dot to get the base URL
|
||||
i2 := strings.IndexByte(u.Host[i1+1:], '.')
|
||||
if i2 < 0 {
|
||||
return Client{}, fmt.Errorf("failed to find '.' in %s", u.Host[i1+1:])
|
||||
}
|
||||
|
||||
sasToken := u.Query()
|
||||
c := newSASClient(u.Host[:i1], u.Host[i1+i2+2:], sasToken)
|
||||
if spr := sasToken.Get("spr"); spr == "" {
|
||||
// infer from URL if not in the query params set
|
||||
c.useHTTPS = u.Scheme == "https"
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c Client) isServiceSASClient() bool {
|
||||
return c.sasClient && c.accountSASToken == nil
|
||||
}
|
||||
|
||||
func (c Client) isAccountSASClient() bool {
|
||||
return c.sasClient && c.accountSASToken != nil
|
||||
}
|
||||
|
||||
func (c Client) getDefaultUserAgent() string {
|
||||
return fmt.Sprintf("Go/%s (%s-%s) azure-storage-go/%s api-version/%s",
|
||||
runtime.Version(),
|
||||
runtime.GOARCH,
|
||||
runtime.GOOS,
|
||||
version.Number,
|
||||
c.apiVersion,
|
||||
)
|
||||
}
|
||||
|
||||
// AddToUserAgent adds an extension to the current user agent
|
||||
func (c *Client) AddToUserAgent(extension string) error {
|
||||
if extension != "" {
|
||||
c.userAgent = fmt.Sprintf("%s %s", c.userAgent, extension)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Extension was empty, User Agent stayed as %s", c.userAgent)
|
||||
}
|
||||
|
||||
// protectUserAgent is used in funcs that include extraheaders as a parameter.
|
||||
// It prevents the User-Agent header to be overwritten, instead if it happens to
|
||||
// be present, it gets added to the current User-Agent. Use it before getStandardHeaders
|
||||
func (c *Client) protectUserAgent(extraheaders map[string]string) map[string]string {
|
||||
if v, ok := extraheaders[userAgentHeader]; ok {
|
||||
c.AddToUserAgent(v)
|
||||
delete(extraheaders, userAgentHeader)
|
||||
}
|
||||
return extraheaders
|
||||
}
|
||||
|
||||
func (c Client) getBaseURL(service string) *url.URL {
|
||||
scheme := "http"
|
||||
if c.useHTTPS {
|
||||
scheme = "https"
|
||||
}
|
||||
host := ""
|
||||
if c.accountName == StorageEmulatorAccountName {
|
||||
switch service {
|
||||
case blobServiceName:
|
||||
host = storageEmulatorBlob
|
||||
case tableServiceName:
|
||||
host = storageEmulatorTable
|
||||
case queueServiceName:
|
||||
host = storageEmulatorQueue
|
||||
}
|
||||
} else {
|
||||
host = fmt.Sprintf("%s.%s.%s", c.accountName, service, c.baseURL)
|
||||
}
|
||||
|
||||
return &url.URL{
|
||||
Scheme: scheme,
|
||||
Host: host,
|
||||
}
|
||||
}
|
||||
|
||||
func (c Client) getEndpoint(service, path string, params url.Values) string {
|
||||
u := c.getBaseURL(service)
|
||||
|
||||
// API doesn't accept path segments not starting with '/'
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = fmt.Sprintf("/%v", path)
|
||||
}
|
||||
|
||||
if c.accountName == StorageEmulatorAccountName {
|
||||
path = fmt.Sprintf("/%v%v", StorageEmulatorAccountName, path)
|
||||
}
|
||||
|
||||
u.Path = path
|
||||
u.RawQuery = params.Encode()
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// AccountSASTokenOptions includes options for constructing
|
||||
// an account SAS token.
|
||||
// https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-an-account-sas
|
||||
type AccountSASTokenOptions struct {
|
||||
APIVersion string
|
||||
Services Services
|
||||
ResourceTypes ResourceTypes
|
||||
Permissions Permissions
|
||||
Start time.Time
|
||||
Expiry time.Time
|
||||
IP string
|
||||
UseHTTPS bool
|
||||
}
|
||||
|
||||
// Services specify services accessible with an account SAS.
|
||||
type Services struct {
|
||||
Blob bool
|
||||
Queue bool
|
||||
Table bool
|
||||
File bool
|
||||
}
|
||||
|
||||
// ResourceTypes specify the resources accesible with an
|
||||
// account SAS.
|
||||
type ResourceTypes struct {
|
||||
Service bool
|
||||
Container bool
|
||||
Object bool
|
||||
}
|
||||
|
||||
// Permissions specifies permissions for an accountSAS.
|
||||
type Permissions struct {
|
||||
Read bool
|
||||
Write bool
|
||||
Delete bool
|
||||
List bool
|
||||
Add bool
|
||||
Create bool
|
||||
Update bool
|
||||
Process bool
|
||||
}
|
||||
|
||||
// GetAccountSASToken creates an account SAS token
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-an-account-sas
|
||||
func (c Client) GetAccountSASToken(options AccountSASTokenOptions) (url.Values, error) {
|
||||
if options.APIVersion == "" {
|
||||
options.APIVersion = c.apiVersion
|
||||
}
|
||||
|
||||
if options.APIVersion < "2015-04-05" {
|
||||
return url.Values{}, fmt.Errorf("account SAS does not support API versions prior to 2015-04-05. API version : %s", options.APIVersion)
|
||||
}
|
||||
|
||||
// build services string
|
||||
services := ""
|
||||
if options.Services.Blob {
|
||||
services += "b"
|
||||
}
|
||||
if options.Services.Queue {
|
||||
services += "q"
|
||||
}
|
||||
if options.Services.Table {
|
||||
services += "t"
|
||||
}
|
||||
if options.Services.File {
|
||||
services += "f"
|
||||
}
|
||||
|
||||
// build resources string
|
||||
resources := ""
|
||||
if options.ResourceTypes.Service {
|
||||
resources += "s"
|
||||
}
|
||||
if options.ResourceTypes.Container {
|
||||
resources += "c"
|
||||
}
|
||||
if options.ResourceTypes.Object {
|
||||
resources += "o"
|
||||
}
|
||||
|
||||
// build permissions string
|
||||
permissions := ""
|
||||
if options.Permissions.Read {
|
||||
permissions += "r"
|
||||
}
|
||||
if options.Permissions.Write {
|
||||
permissions += "w"
|
||||
}
|
||||
if options.Permissions.Delete {
|
||||
permissions += "d"
|
||||
}
|
||||
if options.Permissions.List {
|
||||
permissions += "l"
|
||||
}
|
||||
if options.Permissions.Add {
|
||||
permissions += "a"
|
||||
}
|
||||
if options.Permissions.Create {
|
||||
permissions += "c"
|
||||
}
|
||||
if options.Permissions.Update {
|
||||
permissions += "u"
|
||||
}
|
||||
if options.Permissions.Process {
|
||||
permissions += "p"
|
||||
}
|
||||
|
||||
// build start time, if exists
|
||||
start := ""
|
||||
if options.Start != (time.Time{}) {
|
||||
start = options.Start.UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
// build expiry time
|
||||
expiry := options.Expiry.UTC().Format(time.RFC3339)
|
||||
|
||||
protocol := "https,http"
|
||||
if options.UseHTTPS {
|
||||
protocol = "https"
|
||||
}
|
||||
|
||||
stringToSign := strings.Join([]string{
|
||||
c.accountName,
|
||||
permissions,
|
||||
services,
|
||||
resources,
|
||||
start,
|
||||
expiry,
|
||||
options.IP,
|
||||
protocol,
|
||||
options.APIVersion,
|
||||
"",
|
||||
}, "\n")
|
||||
signature := c.computeHmac256(stringToSign)
|
||||
|
||||
sasParams := url.Values{
|
||||
"sv": {options.APIVersion},
|
||||
"ss": {services},
|
||||
"srt": {resources},
|
||||
"sp": {permissions},
|
||||
"se": {expiry},
|
||||
"spr": {protocol},
|
||||
"sig": {signature},
|
||||
}
|
||||
if start != "" {
|
||||
sasParams.Add("st", start)
|
||||
}
|
||||
if options.IP != "" {
|
||||
sasParams.Add("sip", options.IP)
|
||||
}
|
||||
|
||||
return sasParams, nil
|
||||
}
|
||||
|
||||
// GetBlobService returns a BlobStorageClient which can operate on the blob
|
||||
// service of the storage account.
|
||||
func (c Client) GetBlobService() BlobStorageClient {
|
||||
b := BlobStorageClient{
|
||||
client: c,
|
||||
}
|
||||
b.client.AddToUserAgent(blobServiceName)
|
||||
b.auth = sharedKey
|
||||
if c.UseSharedKeyLite {
|
||||
b.auth = sharedKeyLite
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// GetQueueService returns a QueueServiceClient which can operate on the queue
|
||||
// service of the storage account.
|
||||
func (c Client) GetQueueService() QueueServiceClient {
|
||||
q := QueueServiceClient{
|
||||
client: c,
|
||||
}
|
||||
q.client.AddToUserAgent(queueServiceName)
|
||||
q.auth = sharedKey
|
||||
if c.UseSharedKeyLite {
|
||||
q.auth = sharedKeyLite
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
// GetTableService returns a TableServiceClient which can operate on the table
|
||||
// service of the storage account.
|
||||
func (c Client) GetTableService() TableServiceClient {
|
||||
t := TableServiceClient{
|
||||
client: c,
|
||||
}
|
||||
t.client.AddToUserAgent(tableServiceName)
|
||||
t.auth = sharedKeyForTable
|
||||
if c.UseSharedKeyLite {
|
||||
t.auth = sharedKeyLiteForTable
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// GetFileService returns a FileServiceClient which can operate on the file
|
||||
// service of the storage account.
|
||||
func (c Client) GetFileService() FileServiceClient {
|
||||
f := FileServiceClient{
|
||||
client: c,
|
||||
}
|
||||
f.client.AddToUserAgent(fileServiceName)
|
||||
f.auth = sharedKey
|
||||
if c.UseSharedKeyLite {
|
||||
f.auth = sharedKeyLite
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (c Client) getStandardHeaders() map[string]string {
|
||||
return map[string]string{
|
||||
userAgentHeader: c.userAgent,
|
||||
"x-ms-version": c.apiVersion,
|
||||
"x-ms-date": currentTimeRfc1123Formatted(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c Client) exec(verb, url string, headers map[string]string, body io.Reader, auth authentication) (*http.Response, error) {
|
||||
headers, err := c.addAuthorizationHeader(verb, url, headers, auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(verb, url, body)
|
||||
if err != nil {
|
||||
return nil, errors.New("azure/storage: error creating request: " + err.Error())
|
||||
}
|
||||
|
||||
// http.NewRequest() will automatically set req.ContentLength for a handful of types
|
||||
// otherwise we will handle here.
|
||||
if req.ContentLength < 1 {
|
||||
if clstr, ok := headers["Content-Length"]; ok {
|
||||
if cl, err := strconv.ParseInt(clstr, 10, 64); err == nil {
|
||||
req.ContentLength = cl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range headers {
|
||||
req.Header[k] = append(req.Header[k], v) // Must bypass case munging present in `Add` by using map functions directly. See https://github.com/Azure/azure-sdk-for-go/issues/645
|
||||
}
|
||||
|
||||
if c.isAccountSASClient() {
|
||||
// append the SAS token to the query params
|
||||
v := req.URL.Query()
|
||||
v = mergeParams(v, c.accountSASToken)
|
||||
req.URL.RawQuery = v.Encode()
|
||||
}
|
||||
|
||||
resp, err := c.Sender.Send(&c, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 && resp.StatusCode <= 505 {
|
||||
return resp, getErrorFromResponse(resp)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c Client) execInternalJSONCommon(verb, url string, headers map[string]string, body io.Reader, auth authentication) (*odataResponse, *http.Request, *http.Response, error) {
|
||||
headers, err := c.addAuthorizationHeader(verb, url, headers, auth)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(verb, url, body)
|
||||
for k, v := range headers {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
|
||||
resp, err := c.Sender.Send(&c, req)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
respToRet := &odataResponse{resp: resp}
|
||||
|
||||
statusCode := resp.StatusCode
|
||||
if statusCode >= 400 && statusCode <= 505 {
|
||||
var respBody []byte
|
||||
respBody, err = readAndCloseBody(resp.Body)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
requestID, date, version := getDebugHeaders(resp.Header)
|
||||
if len(respBody) == 0 {
|
||||
// no error in response body, might happen in HEAD requests
|
||||
err = serviceErrFromStatusCode(resp.StatusCode, resp.Status, requestID, date, version)
|
||||
return respToRet, req, resp, err
|
||||
}
|
||||
// try unmarshal as odata.error json
|
||||
err = json.Unmarshal(respBody, &respToRet.odata)
|
||||
}
|
||||
|
||||
return respToRet, req, resp, err
|
||||
}
|
||||
|
||||
func (c Client) execInternalJSON(verb, url string, headers map[string]string, body io.Reader, auth authentication) (*odataResponse, error) {
|
||||
respToRet, _, _, err := c.execInternalJSONCommon(verb, url, headers, body, auth)
|
||||
return respToRet, err
|
||||
}
|
||||
|
||||
func (c Client) execBatchOperationJSON(verb, url string, headers map[string]string, body io.Reader, auth authentication) (*odataResponse, error) {
|
||||
// execute common query, get back generated request, response etc... for more processing.
|
||||
respToRet, req, resp, err := c.execInternalJSONCommon(verb, url, headers, body, auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// return the OData in the case of executing batch commands.
|
||||
// In this case we need to read the outer batch boundary and contents.
|
||||
// Then we read the changeset information within the batch
|
||||
var respBody []byte
|
||||
respBody, err = readAndCloseBody(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// outer multipart body
|
||||
_, batchHeader, err := mime.ParseMediaType(resp.Header["Content-Type"][0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// batch details.
|
||||
batchBoundary := batchHeader["boundary"]
|
||||
batchPartBuf, changesetBoundary, err := genBatchReader(batchBoundary, respBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// changeset details.
|
||||
err = genChangesetReader(req, respToRet, batchPartBuf, changesetBoundary)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return respToRet, nil
|
||||
}
|
||||
|
||||
func genChangesetReader(req *http.Request, respToRet *odataResponse, batchPartBuf io.Reader, changesetBoundary string) error {
|
||||
changesetMultiReader := multipart.NewReader(batchPartBuf, changesetBoundary)
|
||||
changesetPart, err := changesetMultiReader.NextPart()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
changesetPartBufioReader := bufio.NewReader(changesetPart)
|
||||
changesetResp, err := http.ReadResponse(changesetPartBufioReader, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if changesetResp.StatusCode != http.StatusNoContent {
|
||||
changesetBody, err := readAndCloseBody(changesetResp.Body)
|
||||
err = json.Unmarshal(changesetBody, &respToRet.odata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
respToRet.resp = changesetResp
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func genBatchReader(batchBoundary string, respBody []byte) (io.Reader, string, error) {
|
||||
respBodyString := string(respBody)
|
||||
respBodyReader := strings.NewReader(respBodyString)
|
||||
|
||||
// reading batchresponse
|
||||
batchMultiReader := multipart.NewReader(respBodyReader, batchBoundary)
|
||||
batchPart, err := batchMultiReader.NextPart()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
batchPartBufioReader := bufio.NewReader(batchPart)
|
||||
|
||||
_, changesetHeader, err := mime.ParseMediaType(batchPart.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
changesetBoundary := changesetHeader["boundary"]
|
||||
return batchPartBufioReader, changesetBoundary, nil
|
||||
}
|
||||
|
||||
func readAndCloseBody(body io.ReadCloser) ([]byte, error) {
|
||||
defer body.Close()
|
||||
out, err := ioutil.ReadAll(body)
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
|
||||
// reads the response body then closes it
|
||||
func drainRespBody(resp *http.Response) {
|
||||
io.Copy(ioutil.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
}
|
||||
|
||||
func serviceErrFromXML(body []byte, storageErr *AzureStorageServiceError) error {
|
||||
if err := xml.Unmarshal(body, storageErr); err != nil {
|
||||
storageErr.Message = fmt.Sprintf("Response body could no be unmarshaled: %v. Body: %v.", err, string(body))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func serviceErrFromJSON(body []byte, storageErr *AzureStorageServiceError) error {
|
||||
odataError := odataErrorWrapper{}
|
||||
if err := json.Unmarshal(body, &odataError); err != nil {
|
||||
storageErr.Message = fmt.Sprintf("Response body could no be unmarshaled: %v. Body: %v.", err, string(body))
|
||||
return err
|
||||
}
|
||||
storageErr.Code = odataError.Err.Code
|
||||
storageErr.Message = odataError.Err.Message.Value
|
||||
storageErr.Lang = odataError.Err.Message.Lang
|
||||
return nil
|
||||
}
|
||||
|
||||
func serviceErrFromStatusCode(code int, status string, requestID, date, version string) AzureStorageServiceError {
|
||||
return AzureStorageServiceError{
|
||||
StatusCode: code,
|
||||
Code: status,
|
||||
RequestID: requestID,
|
||||
Date: date,
|
||||
APIVersion: version,
|
||||
Message: "no response body was available for error status code",
|
||||
}
|
||||
}
|
||||
|
||||
func (e AzureStorageServiceError) Error() string {
|
||||
return fmt.Sprintf("storage: service returned error: StatusCode=%d, ErrorCode=%s, ErrorMessage=%s, RequestInitiated=%s, RequestId=%s, API Version=%s, QueryParameterName=%s, QueryParameterValue=%s",
|
||||
e.StatusCode, e.Code, e.Message, e.Date, e.RequestID, e.APIVersion, e.QueryParameterName, e.QueryParameterValue)
|
||||
}
|
||||
|
||||
// checkRespCode returns UnexpectedStatusError if the given response code is not
|
||||
// one of the allowed status codes; otherwise nil.
|
||||
func checkRespCode(resp *http.Response, allowed []int) error {
|
||||
for _, v := range allowed {
|
||||
if resp.StatusCode == v {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
err := getErrorFromResponse(resp)
|
||||
return UnexpectedStatusCodeError{
|
||||
allowed: allowed,
|
||||
got: resp.StatusCode,
|
||||
inner: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (c Client) addMetadataToHeaders(h map[string]string, metadata map[string]string) map[string]string {
|
||||
metadata = c.protectUserAgent(metadata)
|
||||
for k, v := range metadata {
|
||||
h[userDefinedMetadataHeaderPrefix+k] = v
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func getDebugHeaders(h http.Header) (requestID, date, version string) {
|
||||
requestID = h.Get("x-ms-request-id")
|
||||
version = h.Get("x-ms-version")
|
||||
date = h.Get("Date")
|
||||
return
|
||||
}
|
||||
|
||||
func getErrorFromResponse(resp *http.Response) error {
|
||||
respBody, err := readAndCloseBody(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
requestID, date, version := getDebugHeaders(resp.Header)
|
||||
if len(respBody) == 0 {
|
||||
// no error in response body, might happen in HEAD requests
|
||||
err = serviceErrFromStatusCode(resp.StatusCode, resp.Status, requestID, date, version)
|
||||
} else {
|
||||
storageErr := AzureStorageServiceError{
|
||||
StatusCode: resp.StatusCode,
|
||||
RequestID: requestID,
|
||||
Date: date,
|
||||
APIVersion: version,
|
||||
}
|
||||
// response contains storage service error object, unmarshal
|
||||
if resp.Header.Get("Content-Type") == "application/xml" {
|
||||
errIn := serviceErrFromXML(respBody, &storageErr)
|
||||
if err != nil { // error unmarshaling the error response
|
||||
err = errIn
|
||||
}
|
||||
} else {
|
||||
errIn := serviceErrFromJSON(respBody, &storageErr)
|
||||
if err != nil { // error unmarshaling the error response
|
||||
err = errIn
|
||||
}
|
||||
}
|
||||
err = storageErr
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
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 (
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SASOptions includes options used by SAS URIs for different
|
||||
// services and resources.
|
||||
type SASOptions struct {
|
||||
APIVersion string
|
||||
Start time.Time
|
||||
Expiry time.Time
|
||||
IP string
|
||||
UseHTTPS bool
|
||||
Identifier string
|
||||
}
|
||||
|
||||
func addQueryParameter(query url.Values, key, value string) url.Values {
|
||||
if value != "" {
|
||||
query.Add(key, value)
|
||||
}
|
||||
return query
|
||||
}
|
|
@ -1,640 +0,0 @@
|
|||
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"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Container represents an Azure container.
|
||||
type Container struct {
|
||||
bsc *BlobStorageClient
|
||||
Name string `xml:"Name"`
|
||||
Properties ContainerProperties `xml:"Properties"`
|
||||
Metadata map[string]string
|
||||
sasuri url.URL
|
||||
}
|
||||
|
||||
// Client returns the HTTP client used by the Container reference.
|
||||
func (c *Container) Client() *Client {
|
||||
return &c.bsc.client
|
||||
}
|
||||
|
||||
func (c *Container) buildPath() string {
|
||||
return fmt.Sprintf("/%s", c.Name)
|
||||
}
|
||||
|
||||
// GetURL gets the canonical URL to the container.
|
||||
// This method does not create a publicly accessible URL if the container
|
||||
// is private and this method does not check if the blob exists.
|
||||
func (c *Container) GetURL() string {
|
||||
container := c.Name
|
||||
if container == "" {
|
||||
container = "$root"
|
||||
}
|
||||
return c.bsc.client.getEndpoint(blobServiceName, pathForResource(container, ""), nil)
|
||||
}
|
||||
|
||||
// ContainerSASOptions are options to construct a container SAS
|
||||
// URI.
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas
|
||||
type ContainerSASOptions struct {
|
||||
ContainerSASPermissions
|
||||
OverrideHeaders
|
||||
SASOptions
|
||||
}
|
||||
|
||||
// ContainerSASPermissions includes the available permissions for
|
||||
// a container SAS URI.
|
||||
type ContainerSASPermissions struct {
|
||||
BlobServiceSASPermissions
|
||||
List bool
|
||||
}
|
||||
|
||||
// GetSASURI creates an URL to the container which contains the Shared
|
||||
// Access Signature with the specified options.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas
|
||||
func (c *Container) GetSASURI(options ContainerSASOptions) (string, error) {
|
||||
uri := c.GetURL()
|
||||
signedResource := "c"
|
||||
canonicalizedResource, err := c.bsc.client.buildCanonicalizedResource(uri, c.bsc.auth, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// build permissions string
|
||||
permissions := options.BlobServiceSASPermissions.buildString()
|
||||
if options.List {
|
||||
permissions += "l"
|
||||
}
|
||||
|
||||
return c.bsc.client.blobAndFileSASURI(options.SASOptions, uri, permissions, canonicalizedResource, signedResource, options.OverrideHeaders)
|
||||
}
|
||||
|
||||
// ContainerProperties contains various properties of a container returned from
|
||||
// various endpoints like ListContainers.
|
||||
type ContainerProperties struct {
|
||||
LastModified string `xml:"Last-Modified"`
|
||||
Etag string `xml:"Etag"`
|
||||
LeaseStatus string `xml:"LeaseStatus"`
|
||||
LeaseState string `xml:"LeaseState"`
|
||||
LeaseDuration string `xml:"LeaseDuration"`
|
||||
PublicAccess ContainerAccessType `xml:"PublicAccess"`
|
||||
}
|
||||
|
||||
// ContainerListResponse contains the response fields from
|
||||
// ListContainers call.
|
||||
//
|
||||
// See https://msdn.microsoft.com/en-us/library/azure/dd179352.aspx
|
||||
type ContainerListResponse struct {
|
||||
XMLName xml.Name `xml:"EnumerationResults"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Prefix string `xml:"Prefix"`
|
||||
Marker string `xml:"Marker"`
|
||||
NextMarker string `xml:"NextMarker"`
|
||||
MaxResults int64 `xml:"MaxResults"`
|
||||
Containers []Container `xml:"Containers>Container"`
|
||||
}
|
||||
|
||||
// BlobListResponse contains the response fields from ListBlobs call.
|
||||
//
|
||||
// See https://msdn.microsoft.com/en-us/library/azure/dd135734.aspx
|
||||
type BlobListResponse struct {
|
||||
XMLName xml.Name `xml:"EnumerationResults"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Prefix string `xml:"Prefix"`
|
||||
Marker string `xml:"Marker"`
|
||||
NextMarker string `xml:"NextMarker"`
|
||||
MaxResults int64 `xml:"MaxResults"`
|
||||
Blobs []Blob `xml:"Blobs>Blob"`
|
||||
|
||||
// BlobPrefix is used to traverse blobs as if it were a file system.
|
||||
// It is returned if ListBlobsParameters.Delimiter is specified.
|
||||
// The list here can be thought of as "folders" that may contain
|
||||
// other folders or blobs.
|
||||
BlobPrefixes []string `xml:"Blobs>BlobPrefix>Name"`
|
||||
|
||||
// Delimiter is used to traverse blobs as if it were a file system.
|
||||
// It is returned if ListBlobsParameters.Delimiter is specified.
|
||||
Delimiter string `xml:"Delimiter"`
|
||||
}
|
||||
|
||||
// IncludeBlobDataset has options to include in a list blobs operation
|
||||
type IncludeBlobDataset struct {
|
||||
Snapshots bool
|
||||
Metadata bool
|
||||
UncommittedBlobs bool
|
||||
Copy bool
|
||||
}
|
||||
|
||||
// ListBlobsParameters defines the set of customizable
|
||||
// parameters to make a List Blobs call.
|
||||
//
|
||||
// See https://msdn.microsoft.com/en-us/library/azure/dd135734.aspx
|
||||
type ListBlobsParameters struct {
|
||||
Prefix string
|
||||
Delimiter string
|
||||
Marker string
|
||||
Include *IncludeBlobDataset
|
||||
MaxResults uint
|
||||
Timeout uint
|
||||
RequestID string
|
||||
}
|
||||
|
||||
func (p ListBlobsParameters) getParameters() url.Values {
|
||||
out := url.Values{}
|
||||
|
||||
if p.Prefix != "" {
|
||||
out.Set("prefix", p.Prefix)
|
||||
}
|
||||
if p.Delimiter != "" {
|
||||
out.Set("delimiter", p.Delimiter)
|
||||
}
|
||||
if p.Marker != "" {
|
||||
out.Set("marker", p.Marker)
|
||||
}
|
||||
if p.Include != nil {
|
||||
include := []string{}
|
||||
include = addString(include, p.Include.Snapshots, "snapshots")
|
||||
include = addString(include, p.Include.Metadata, "metadata")
|
||||
include = addString(include, p.Include.UncommittedBlobs, "uncommittedblobs")
|
||||
include = addString(include, p.Include.Copy, "copy")
|
||||
fullInclude := strings.Join(include, ",")
|
||||
out.Set("include", fullInclude)
|
||||
}
|
||||
if p.MaxResults != 0 {
|
||||
out.Set("maxresults", strconv.FormatUint(uint64(p.MaxResults), 10))
|
||||
}
|
||||
if p.Timeout != 0 {
|
||||
out.Set("timeout", strconv.FormatUint(uint64(p.Timeout), 10))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func addString(datasets []string, include bool, text string) []string {
|
||||
if include {
|
||||
datasets = append(datasets, text)
|
||||
}
|
||||
return datasets
|
||||
}
|
||||
|
||||
// ContainerAccessType defines the access level to the container from a public
|
||||
// request.
|
||||
//
|
||||
// See https://msdn.microsoft.com/en-us/library/azure/dd179468.aspx and "x-ms-
|
||||
// blob-public-access" header.
|
||||
type ContainerAccessType string
|
||||
|
||||
// Access options for containers
|
||||
const (
|
||||
ContainerAccessTypePrivate ContainerAccessType = ""
|
||||
ContainerAccessTypeBlob ContainerAccessType = "blob"
|
||||
ContainerAccessTypeContainer ContainerAccessType = "container"
|
||||
)
|
||||
|
||||
// ContainerAccessPolicy represents each access policy in the container ACL.
|
||||
type ContainerAccessPolicy struct {
|
||||
ID string
|
||||
StartTime time.Time
|
||||
ExpiryTime time.Time
|
||||
CanRead bool
|
||||
CanWrite bool
|
||||
CanDelete bool
|
||||
}
|
||||
|
||||
// ContainerPermissions represents the container ACLs.
|
||||
type ContainerPermissions struct {
|
||||
AccessType ContainerAccessType
|
||||
AccessPolicies []ContainerAccessPolicy
|
||||
}
|
||||
|
||||
// ContainerAccessHeader references header used when setting/getting container ACL
|
||||
const (
|
||||
ContainerAccessHeader string = "x-ms-blob-public-access"
|
||||
)
|
||||
|
||||
// GetBlobReference returns a Blob object for the specified blob name.
|
||||
func (c *Container) GetBlobReference(name string) *Blob {
|
||||
return &Blob{
|
||||
Container: c,
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateContainerOptions includes the options for a create container operation
|
||||
type CreateContainerOptions struct {
|
||||
Timeout uint
|
||||
Access ContainerAccessType `header:"x-ms-blob-public-access"`
|
||||
RequestID string `header:"x-ms-client-request-id"`
|
||||
}
|
||||
|
||||
// Create creates a blob container within the storage account
|
||||
// with given name and access level. Returns error if container already exists.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Create-Container
|
||||
func (c *Container) Create(options *CreateContainerOptions) error {
|
||||
resp, err := c.create(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
return checkRespCode(resp, []int{http.StatusCreated})
|
||||
}
|
||||
|
||||
// CreateIfNotExists creates a blob container if it does not exist. Returns
|
||||
// true if container is newly created or false if container already exists.
|
||||
func (c *Container) CreateIfNotExists(options *CreateContainerOptions) (bool, error) {
|
||||
resp, err := c.create(options)
|
||||
if resp != nil {
|
||||
defer drainRespBody(resp)
|
||||
if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusConflict {
|
||||
return resp.StatusCode == http.StatusCreated, nil
|
||||
}
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func (c *Container) create(options *CreateContainerOptions) (*http.Response, error) {
|
||||
query := url.Values{"restype": {"container"}}
|
||||
headers := c.bsc.client.getStandardHeaders()
|
||||
headers = c.bsc.client.addMetadataToHeaders(headers, c.Metadata)
|
||||
|
||||
if options != nil {
|
||||
query = addTimeout(query, options.Timeout)
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), query)
|
||||
|
||||
return c.bsc.client.exec(http.MethodPut, uri, headers, nil, c.bsc.auth)
|
||||
}
|
||||
|
||||
// Exists returns true if a container with given name exists
|
||||
// on the storage account, otherwise returns false.
|
||||
func (c *Container) Exists() (bool, error) {
|
||||
q := url.Values{"restype": {"container"}}
|
||||
var uri string
|
||||
if c.bsc.client.isServiceSASClient() {
|
||||
q = mergeParams(q, c.sasuri.Query())
|
||||
newURI := c.sasuri
|
||||
newURI.RawQuery = q.Encode()
|
||||
uri = newURI.String()
|
||||
|
||||
} else {
|
||||
uri = c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), q)
|
||||
}
|
||||
headers := c.bsc.client.getStandardHeaders()
|
||||
|
||||
resp, err := c.bsc.client.exec(http.MethodHead, uri, headers, nil, c.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
|
||||
}
|
||||
|
||||
// SetContainerPermissionOptions includes options for a set container permissions operation
|
||||
type SetContainerPermissionOptions struct {
|
||||
Timeout uint
|
||||
LeaseID string `header:"x-ms-lease-id"`
|
||||
IfModifiedSince *time.Time `header:"If-Modified-Since"`
|
||||
IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
|
||||
RequestID string `header:"x-ms-client-request-id"`
|
||||
}
|
||||
|
||||
// SetPermissions sets up container permissions
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Set-Container-ACL
|
||||
func (c *Container) SetPermissions(permissions ContainerPermissions, options *SetContainerPermissionOptions) error {
|
||||
body, length, err := generateContainerACLpayload(permissions.AccessPolicies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
params := url.Values{
|
||||
"restype": {"container"},
|
||||
"comp": {"acl"},
|
||||
}
|
||||
headers := c.bsc.client.getStandardHeaders()
|
||||
headers = addToHeaders(headers, ContainerAccessHeader, string(permissions.AccessType))
|
||||
headers["Content-Length"] = strconv.Itoa(length)
|
||||
|
||||
if options != nil {
|
||||
params = addTimeout(params, options.Timeout)
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
|
||||
|
||||
resp, err := c.bsc.client.exec(http.MethodPut, uri, headers, body, c.bsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
return checkRespCode(resp, []int{http.StatusOK})
|
||||
}
|
||||
|
||||
// GetContainerPermissionOptions includes options for a get container permissions operation
|
||||
type GetContainerPermissionOptions struct {
|
||||
Timeout uint
|
||||
LeaseID string `header:"x-ms-lease-id"`
|
||||
RequestID string `header:"x-ms-client-request-id"`
|
||||
}
|
||||
|
||||
// GetPermissions gets the container permissions as per https://msdn.microsoft.com/en-us/library/azure/dd179469.aspx
|
||||
// If timeout is 0 then it will not be passed to Azure
|
||||
// leaseID will only be passed to Azure if populated
|
||||
func (c *Container) GetPermissions(options *GetContainerPermissionOptions) (*ContainerPermissions, error) {
|
||||
params := url.Values{
|
||||
"restype": {"container"},
|
||||
"comp": {"acl"},
|
||||
}
|
||||
headers := c.bsc.client.getStandardHeaders()
|
||||
|
||||
if options != nil {
|
||||
params = addTimeout(params, options.Timeout)
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
|
||||
|
||||
resp, err := c.bsc.client.exec(http.MethodGet, uri, headers, nil, c.bsc.auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var ap AccessPolicy
|
||||
err = xmlUnmarshal(resp.Body, &ap.SignedIdentifiersList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buildAccessPolicy(ap, &resp.Header), nil
|
||||
}
|
||||
|
||||
func buildAccessPolicy(ap AccessPolicy, headers *http.Header) *ContainerPermissions {
|
||||
// containerAccess. Blob, Container, empty
|
||||
containerAccess := headers.Get(http.CanonicalHeaderKey(ContainerAccessHeader))
|
||||
permissions := ContainerPermissions{
|
||||
AccessType: ContainerAccessType(containerAccess),
|
||||
AccessPolicies: []ContainerAccessPolicy{},
|
||||
}
|
||||
|
||||
for _, policy := range ap.SignedIdentifiersList.SignedIdentifiers {
|
||||
capd := ContainerAccessPolicy{
|
||||
ID: policy.ID,
|
||||
StartTime: policy.AccessPolicy.StartTime,
|
||||
ExpiryTime: policy.AccessPolicy.ExpiryTime,
|
||||
}
|
||||
capd.CanRead = updatePermissions(policy.AccessPolicy.Permission, "r")
|
||||
capd.CanWrite = updatePermissions(policy.AccessPolicy.Permission, "w")
|
||||
capd.CanDelete = updatePermissions(policy.AccessPolicy.Permission, "d")
|
||||
|
||||
permissions.AccessPolicies = append(permissions.AccessPolicies, capd)
|
||||
}
|
||||
return &permissions
|
||||
}
|
||||
|
||||
// DeleteContainerOptions includes options for a delete container operation
|
||||
type DeleteContainerOptions struct {
|
||||
Timeout uint
|
||||
LeaseID string `header:"x-ms-lease-id"`
|
||||
IfModifiedSince *time.Time `header:"If-Modified-Since"`
|
||||
IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
|
||||
RequestID string `header:"x-ms-client-request-id"`
|
||||
}
|
||||
|
||||
// Delete deletes the container with given name on the storage
|
||||
// account. If the container does not exist returns error.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/delete-container
|
||||
func (c *Container) Delete(options *DeleteContainerOptions) error {
|
||||
resp, err := c.delete(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
return checkRespCode(resp, []int{http.StatusAccepted})
|
||||
}
|
||||
|
||||
// DeleteIfExists deletes the container with given name on the storage
|
||||
// account if it exists. Returns true if container is deleted with this call, or
|
||||
// false if the container did not exist at the time of the Delete Container
|
||||
// operation.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/delete-container
|
||||
func (c *Container) DeleteIfExists(options *DeleteContainerOptions) (bool, error) {
|
||||
resp, err := c.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 (c *Container) delete(options *DeleteContainerOptions) (*http.Response, error) {
|
||||
query := url.Values{"restype": {"container"}}
|
||||
headers := c.bsc.client.getStandardHeaders()
|
||||
|
||||
if options != nil {
|
||||
query = addTimeout(query, options.Timeout)
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), query)
|
||||
|
||||
return c.bsc.client.exec(http.MethodDelete, uri, headers, nil, c.bsc.auth)
|
||||
}
|
||||
|
||||
// ListBlobs returns an object that contains list of blobs in the container,
|
||||
// pagination token and other information in the response of List Blobs call.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/List-Blobs
|
||||
func (c *Container) ListBlobs(params ListBlobsParameters) (BlobListResponse, error) {
|
||||
q := mergeParams(params.getParameters(), url.Values{
|
||||
"restype": {"container"},
|
||||
"comp": {"list"},
|
||||
})
|
||||
var uri string
|
||||
if c.bsc.client.isServiceSASClient() {
|
||||
q = mergeParams(q, c.sasuri.Query())
|
||||
newURI := c.sasuri
|
||||
newURI.RawQuery = q.Encode()
|
||||
uri = newURI.String()
|
||||
} else {
|
||||
uri = c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), q)
|
||||
}
|
||||
|
||||
headers := c.bsc.client.getStandardHeaders()
|
||||
headers = addToHeaders(headers, "x-ms-client-request-id", params.RequestID)
|
||||
|
||||
var out BlobListResponse
|
||||
resp, err := c.bsc.client.exec(http.MethodGet, uri, headers, nil, c.bsc.auth)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
for i := range out.Blobs {
|
||||
out.Blobs[i].Container = c
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
|
||||
// ContainerMetadataOptions includes options for container metadata operations
|
||||
type ContainerMetadataOptions struct {
|
||||
Timeout uint
|
||||
LeaseID string `header:"x-ms-lease-id"`
|
||||
RequestID string `header:"x-ms-client-request-id"`
|
||||
}
|
||||
|
||||
// SetMetadata replaces the metadata for the specified container.
|
||||
//
|
||||
// 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://docs.microsoft.com/en-us/rest/api/storageservices/set-container-metadata
|
||||
func (c *Container) SetMetadata(options *ContainerMetadataOptions) error {
|
||||
params := url.Values{
|
||||
"comp": {"metadata"},
|
||||
"restype": {"container"},
|
||||
}
|
||||
headers := c.bsc.client.getStandardHeaders()
|
||||
headers = c.bsc.client.addMetadataToHeaders(headers, c.Metadata)
|
||||
|
||||
if options != nil {
|
||||
params = addTimeout(params, options.Timeout)
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
|
||||
uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
|
||||
|
||||
resp, err := c.bsc.client.exec(http.MethodPut, uri, headers, nil, c.bsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
return checkRespCode(resp, []int{http.StatusOK})
|
||||
}
|
||||
|
||||
// GetMetadata returns all user-defined metadata for the specified container.
|
||||
//
|
||||
// All metadata keys will be returned in lower case. (HTTP header
|
||||
// names are case-insensitive.)
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/get-container-metadata
|
||||
func (c *Container) GetMetadata(options *ContainerMetadataOptions) error {
|
||||
params := url.Values{
|
||||
"comp": {"metadata"},
|
||||
"restype": {"container"},
|
||||
}
|
||||
headers := c.bsc.client.getStandardHeaders()
|
||||
|
||||
if options != nil {
|
||||
params = addTimeout(params, options.Timeout)
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
|
||||
uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
|
||||
|
||||
resp, err := c.bsc.client.exec(http.MethodGet, uri, headers, nil, c.bsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
if err := checkRespCode(resp, []int{http.StatusOK}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.writeMetadata(resp.Header)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Container) writeMetadata(h http.Header) {
|
||||
c.Metadata = writeMetadata(h)
|
||||
}
|
||||
|
||||
func generateContainerACLpayload(policies []ContainerAccessPolicy) (io.Reader, int, error) {
|
||||
sil := SignedIdentifiers{
|
||||
SignedIdentifiers: []SignedIdentifier{},
|
||||
}
|
||||
for _, capd := range policies {
|
||||
permission := capd.generateContainerPermissions()
|
||||
signedIdentifier := convertAccessPolicyToXMLStructs(capd.ID, capd.StartTime, capd.ExpiryTime, permission)
|
||||
sil.SignedIdentifiers = append(sil.SignedIdentifiers, signedIdentifier)
|
||||
}
|
||||
return xmlMarshal(sil)
|
||||
}
|
||||
|
||||
func (capd *ContainerAccessPolicy) generateContainerPermissions() (permissions string) {
|
||||
// generate the permissions string (rwd).
|
||||
// still want the end user API to have bool flags.
|
||||
permissions = ""
|
||||
|
||||
if capd.CanRead {
|
||||
permissions += "r"
|
||||
}
|
||||
|
||||
if capd.CanWrite {
|
||||
permissions += "w"
|
||||
}
|
||||
|
||||
if capd.CanDelete {
|
||||
permissions += "d"
|
||||
}
|
||||
|
||||
return permissions
|
||||
}
|
||||
|
||||
// GetProperties updated the properties of the container.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/get-container-properties
|
||||
func (c *Container) GetProperties() error {
|
||||
params := url.Values{
|
||||
"restype": {"container"},
|
||||
}
|
||||
headers := c.bsc.client.getStandardHeaders()
|
||||
|
||||
uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
|
||||
|
||||
resp, err := c.bsc.client.exec(http.MethodGet, uri, headers, nil, c.bsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if err := checkRespCode(resp, []int{http.StatusOK}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update properties
|
||||
c.Properties.Etag = resp.Header.Get(headerEtag)
|
||||
c.Properties.LeaseStatus = resp.Header.Get("x-ms-lease-status")
|
||||
c.Properties.LeaseState = resp.Header.Get("x-ms-lease-state")
|
||||
c.Properties.LeaseDuration = resp.Header.Get("x-ms-lease-duration")
|
||||
c.Properties.LastModified = resp.Header.Get("Last-Modified")
|
||||
c.Properties.PublicAccess = ContainerAccessType(resp.Header.Get(ContainerAccessHeader))
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,237 +0,0 @@
|
|||
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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
blobCopyStatusPending = "pending"
|
||||
blobCopyStatusSuccess = "success"
|
||||
blobCopyStatusAborted = "aborted"
|
||||
blobCopyStatusFailed = "failed"
|
||||
)
|
||||
|
||||
// CopyOptions includes the options for a copy blob operation
|
||||
type CopyOptions struct {
|
||||
Timeout uint
|
||||
Source CopyOptionsConditions
|
||||
Destiny CopyOptionsConditions
|
||||
RequestID string
|
||||
}
|
||||
|
||||
// IncrementalCopyOptions includes the options for an incremental copy blob operation
|
||||
type IncrementalCopyOptions struct {
|
||||
Timeout uint
|
||||
Destination IncrementalCopyOptionsConditions
|
||||
RequestID string
|
||||
}
|
||||
|
||||
// CopyOptionsConditions includes some conditional options in a copy blob operation
|
||||
type CopyOptionsConditions struct {
|
||||
LeaseID string
|
||||
IfModifiedSince *time.Time
|
||||
IfUnmodifiedSince *time.Time
|
||||
IfMatch string
|
||||
IfNoneMatch string
|
||||
}
|
||||
|
||||
// IncrementalCopyOptionsConditions includes some conditional options in a copy blob operation
|
||||
type IncrementalCopyOptionsConditions struct {
|
||||
IfModifiedSince *time.Time
|
||||
IfUnmodifiedSince *time.Time
|
||||
IfMatch string
|
||||
IfNoneMatch string
|
||||
}
|
||||
|
||||
// Copy starts a blob copy operation and waits for the operation to
|
||||
// complete. sourceBlob parameter must be a canonical URL to the blob (can be
|
||||
// obtained using the GetURL method.) There is no SLA on blob copy and therefore
|
||||
// this helper method works faster on smaller files.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Copy-Blob
|
||||
func (b *Blob) Copy(sourceBlob string, options *CopyOptions) error {
|
||||
copyID, err := b.StartCopy(sourceBlob, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.WaitForCopy(copyID)
|
||||
}
|
||||
|
||||
// StartCopy starts a blob copy operation.
|
||||
// sourceBlob parameter must be a canonical URL to the blob (can be
|
||||
// obtained using the GetURL method.)
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Copy-Blob
|
||||
func (b *Blob) StartCopy(sourceBlob string, options *CopyOptions) (string, error) {
|
||||
params := url.Values{}
|
||||
headers := b.Container.bsc.client.getStandardHeaders()
|
||||
headers["x-ms-copy-source"] = sourceBlob
|
||||
headers = b.Container.bsc.client.addMetadataToHeaders(headers, b.Metadata)
|
||||
|
||||
if options != nil {
|
||||
params = addTimeout(params, options.Timeout)
|
||||
headers = addToHeaders(headers, "x-ms-client-request-id", options.RequestID)
|
||||
// source
|
||||
headers = addToHeaders(headers, "x-ms-source-lease-id", options.Source.LeaseID)
|
||||
headers = addTimeToHeaders(headers, "x-ms-source-if-modified-since", options.Source.IfModifiedSince)
|
||||
headers = addTimeToHeaders(headers, "x-ms-source-if-unmodified-since", options.Source.IfUnmodifiedSince)
|
||||
headers = addToHeaders(headers, "x-ms-source-if-match", options.Source.IfMatch)
|
||||
headers = addToHeaders(headers, "x-ms-source-if-none-match", options.Source.IfNoneMatch)
|
||||
//destiny
|
||||
headers = addToHeaders(headers, "x-ms-lease-id", options.Destiny.LeaseID)
|
||||
headers = addTimeToHeaders(headers, "x-ms-if-modified-since", options.Destiny.IfModifiedSince)
|
||||
headers = addTimeToHeaders(headers, "x-ms-if-unmodified-since", options.Destiny.IfUnmodifiedSince)
|
||||
headers = addToHeaders(headers, "x-ms-if-match", options.Destiny.IfMatch)
|
||||
headers = addToHeaders(headers, "x-ms-if-none-match", options.Destiny.IfNoneMatch)
|
||||
}
|
||||
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)
|
||||
|
||||
if err := checkRespCode(resp, []int{http.StatusAccepted, http.StatusCreated}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
copyID := resp.Header.Get("x-ms-copy-id")
|
||||
if copyID == "" {
|
||||
return "", errors.New("Got empty copy id header")
|
||||
}
|
||||
return copyID, nil
|
||||
}
|
||||
|
||||
// AbortCopyOptions includes the options for an abort blob operation
|
||||
type AbortCopyOptions struct {
|
||||
Timeout uint
|
||||
LeaseID string `header:"x-ms-lease-id"`
|
||||
RequestID string `header:"x-ms-client-request-id"`
|
||||
}
|
||||
|
||||
// AbortCopy aborts a BlobCopy which has already been triggered by the StartBlobCopy function.
|
||||
// copyID is generated from StartBlobCopy function.
|
||||
// currentLeaseID is required IF the destination blob has an active lease on it.
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Abort-Copy-Blob
|
||||
func (b *Blob) AbortCopy(copyID string, options *AbortCopyOptions) error {
|
||||
params := url.Values{
|
||||
"comp": {"copy"},
|
||||
"copyid": {copyID},
|
||||
}
|
||||
headers := b.Container.bsc.client.getStandardHeaders()
|
||||
headers["x-ms-copy-action"] = "abort"
|
||||
|
||||
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.StatusNoContent})
|
||||
}
|
||||
|
||||
// WaitForCopy loops until a BlobCopy operation is completed (or fails with error)
|
||||
func (b *Blob) WaitForCopy(copyID string) error {
|
||||
for {
|
||||
err := b.GetProperties(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b.Properties.CopyID != copyID {
|
||||
return errBlobCopyIDMismatch
|
||||
}
|
||||
|
||||
switch b.Properties.CopyStatus {
|
||||
case blobCopyStatusSuccess:
|
||||
return nil
|
||||
case blobCopyStatusPending:
|
||||
continue
|
||||
case blobCopyStatusAborted:
|
||||
return errBlobCopyAborted
|
||||
case blobCopyStatusFailed:
|
||||
return fmt.Errorf("storage: blob copy failed. Id=%s Description=%s", b.Properties.CopyID, b.Properties.CopyStatusDescription)
|
||||
default:
|
||||
return fmt.Errorf("storage: unhandled blob copy status: '%s'", b.Properties.CopyStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IncrementalCopyBlob copies a snapshot of a source blob and copies to referring blob
|
||||
// sourceBlob parameter must be a valid snapshot URL of the original blob.
|
||||
// THe original blob mut be public, or use a Shared Access Signature.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/incremental-copy-blob .
|
||||
func (b *Blob) IncrementalCopyBlob(sourceBlobURL string, snapshotTime time.Time, options *IncrementalCopyOptions) (string, error) {
|
||||
params := url.Values{"comp": {"incrementalcopy"}}
|
||||
|
||||
// need formatting to 7 decimal places so it's friendly to Windows and *nix
|
||||
snapshotTimeFormatted := snapshotTime.Format("2006-01-02T15:04:05.0000000Z")
|
||||
u, err := url.Parse(sourceBlobURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
query := u.Query()
|
||||
query.Add("snapshot", snapshotTimeFormatted)
|
||||
encodedQuery := query.Encode()
|
||||
encodedQuery = strings.Replace(encodedQuery, "%3A", ":", -1)
|
||||
u.RawQuery = encodedQuery
|
||||
snapshotURL := u.String()
|
||||
|
||||
headers := b.Container.bsc.client.getStandardHeaders()
|
||||
headers["x-ms-copy-source"] = snapshotURL
|
||||
|
||||
if options != nil {
|
||||
addTimeout(params, options.Timeout)
|
||||
headers = addToHeaders(headers, "x-ms-client-request-id", options.RequestID)
|
||||
headers = addTimeToHeaders(headers, "x-ms-if-modified-since", options.Destination.IfModifiedSince)
|
||||
headers = addTimeToHeaders(headers, "x-ms-if-unmodified-since", options.Destination.IfUnmodifiedSince)
|
||||
headers = addToHeaders(headers, "x-ms-if-match", options.Destination.IfMatch)
|
||||
headers = addToHeaders(headers, "x-ms-if-none-match", options.Destination.IfNoneMatch)
|
||||
}
|
||||
|
||||
// get URI of destination blob
|
||||
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)
|
||||
|
||||
if err := checkRespCode(resp, []int{http.StatusAccepted}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
copyID := resp.Header.Get("x-ms-copy-id")
|
||||
if copyID == "" {
|
||||
return "", errors.New("Got empty copy id header")
|
||||
}
|
||||
return copyID, nil
|
||||
}
|
|
@ -1,238 +0,0 @@
|
|||
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"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Directory represents a directory on a share.
|
||||
type Directory struct {
|
||||
fsc *FileServiceClient
|
||||
Metadata map[string]string
|
||||
Name string `xml:"Name"`
|
||||
parent *Directory
|
||||
Properties DirectoryProperties
|
||||
share *Share
|
||||
}
|
||||
|
||||
// DirectoryProperties contains various properties of a directory.
|
||||
type DirectoryProperties struct {
|
||||
LastModified string `xml:"Last-Modified"`
|
||||
Etag string `xml:"Etag"`
|
||||
}
|
||||
|
||||
// ListDirsAndFilesParameters defines the set of customizable parameters to
|
||||
// make a List Files and Directories call.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/List-Directories-and-Files
|
||||
type ListDirsAndFilesParameters struct {
|
||||
Prefix string
|
||||
Marker string
|
||||
MaxResults uint
|
||||
Timeout uint
|
||||
}
|
||||
|
||||
// DirsAndFilesListResponse contains the response fields from
|
||||
// a List Files and Directories call.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/List-Directories-and-Files
|
||||
type DirsAndFilesListResponse struct {
|
||||
XMLName xml.Name `xml:"EnumerationResults"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Marker string `xml:"Marker"`
|
||||
MaxResults int64 `xml:"MaxResults"`
|
||||
Directories []Directory `xml:"Entries>Directory"`
|
||||
Files []File `xml:"Entries>File"`
|
||||
NextMarker string `xml:"NextMarker"`
|
||||
}
|
||||
|
||||
// builds the complete directory path for this directory object.
|
||||
func (d *Directory) buildPath() string {
|
||||
path := ""
|
||||
current := d
|
||||
for current.Name != "" {
|
||||
path = "/" + current.Name + path
|
||||
current = current.parent
|
||||
}
|
||||
return d.share.buildPath() + path
|
||||
}
|
||||
|
||||
// Create this directory in the associated share.
|
||||
// If a directory with the same name already exists, the operation fails.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Create-Directory
|
||||
func (d *Directory) Create(options *FileRequestOptions) error {
|
||||
// if this is the root directory exit early
|
||||
if d.parent == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
params := prepareOptions(options)
|
||||
headers, err := d.fsc.createResource(d.buildPath(), resourceDirectory, params, mergeMDIntoExtraHeaders(d.Metadata, nil), []int{http.StatusCreated})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.updateEtagAndLastModified(headers)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateIfNotExists creates this directory under the associated share if the
|
||||
// directory does not exists. Returns true if the directory is newly created or
|
||||
// false if the directory already exists.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Create-Directory
|
||||
func (d *Directory) CreateIfNotExists(options *FileRequestOptions) (bool, error) {
|
||||
// if this is the root directory exit early
|
||||
if d.parent == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
params := prepareOptions(options)
|
||||
resp, err := d.fsc.createResourceNoClose(d.buildPath(), resourceDirectory, params, nil)
|
||||
if resp != nil {
|
||||
defer drainRespBody(resp)
|
||||
if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusConflict {
|
||||
if resp.StatusCode == http.StatusCreated {
|
||||
d.updateEtagAndLastModified(resp.Header)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, d.FetchAttributes(nil)
|
||||
}
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Delete removes this directory. It must be empty in order to be deleted.
|
||||
// If the directory does not exist the operation fails.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Delete-Directory
|
||||
func (d *Directory) Delete(options *FileRequestOptions) error {
|
||||
return d.fsc.deleteResource(d.buildPath(), resourceDirectory, options)
|
||||
}
|
||||
|
||||
// DeleteIfExists removes this directory if it exists.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Delete-Directory
|
||||
func (d *Directory) DeleteIfExists(options *FileRequestOptions) (bool, error) {
|
||||
resp, err := d.fsc.deleteResourceNoClose(d.buildPath(), resourceDirectory, 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
|
||||
}
|
||||
|
||||
// Exists returns true if this directory exists.
|
||||
func (d *Directory) Exists() (bool, error) {
|
||||
exists, headers, err := d.fsc.resourceExists(d.buildPath(), resourceDirectory)
|
||||
if exists {
|
||||
d.updateEtagAndLastModified(headers)
|
||||
}
|
||||
return exists, err
|
||||
}
|
||||
|
||||
// FetchAttributes retrieves metadata for this directory.
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-directory-properties
|
||||
func (d *Directory) FetchAttributes(options *FileRequestOptions) error {
|
||||
params := prepareOptions(options)
|
||||
headers, err := d.fsc.getResourceHeaders(d.buildPath(), compNone, resourceDirectory, params, http.MethodHead)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.updateEtagAndLastModified(headers)
|
||||
d.Metadata = getMetadataFromHeaders(headers)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDirectoryReference returns a child Directory object for this directory.
|
||||
func (d *Directory) GetDirectoryReference(name string) *Directory {
|
||||
return &Directory{
|
||||
fsc: d.fsc,
|
||||
Name: name,
|
||||
parent: d,
|
||||
share: d.share,
|
||||
}
|
||||
}
|
||||
|
||||
// GetFileReference returns a child File object for this directory.
|
||||
func (d *Directory) GetFileReference(name string) *File {
|
||||
return &File{
|
||||
fsc: d.fsc,
|
||||
Name: name,
|
||||
parent: d,
|
||||
share: d.share,
|
||||
mutex: &sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
// ListDirsAndFiles returns a list of files and directories under this directory.
|
||||
// It also contains a pagination token and other response details.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/List-Directories-and-Files
|
||||
func (d *Directory) ListDirsAndFiles(params ListDirsAndFilesParameters) (*DirsAndFilesListResponse, error) {
|
||||
q := mergeParams(params.getParameters(), getURLInitValues(compList, resourceDirectory))
|
||||
|
||||
resp, err := d.fsc.listContent(d.buildPath(), q, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
var out DirsAndFilesListResponse
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
return &out, err
|
||||
}
|
||||
|
||||
// SetMetadata replaces the metadata for this directory.
|
||||
//
|
||||
// Some keys may be converted to Camel-Case before sending. All keys
|
||||
// are returned in lower case by GetDirectoryMetadata. 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-Directory-Metadata
|
||||
func (d *Directory) SetMetadata(options *FileRequestOptions) error {
|
||||
headers, err := d.fsc.setResourceHeaders(d.buildPath(), compMetadata, resourceDirectory, mergeMDIntoExtraHeaders(d.Metadata, nil), options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.updateEtagAndLastModified(headers)
|
||||
return nil
|
||||
}
|
||||
|
||||
// updates Etag and last modified date
|
||||
func (d *Directory) updateEtagAndLastModified(headers http.Header) {
|
||||
d.Properties.Etag = headers.Get("Etag")
|
||||
d.Properties.LastModified = headers.Get("Last-Modified")
|
||||
}
|
||||
|
||||
// URL gets the canonical URL to this directory.
|
||||
// This method does not create a publicly accessible URL if the directory
|
||||
// is private and this method does not check if the directory exists.
|
||||
func (d *Directory) URL() string {
|
||||
return d.fsc.client.getEndpoint(fileServiceName, d.buildPath(), url.Values{})
|
||||
}
|
|
@ -1,466 +0,0 @@
|
|||
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 (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
uuid "github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
// Annotating as secure for gas scanning
|
||||
/* #nosec */
|
||||
const (
|
||||
partitionKeyNode = "PartitionKey"
|
||||
rowKeyNode = "RowKey"
|
||||
etagErrorTemplate = "Etag didn't match: %v"
|
||||
)
|
||||
|
||||
var (
|
||||
errEmptyPayload = errors.New("Empty payload is not a valid metadata level for this operation")
|
||||
errNilPreviousResult = errors.New("The previous results page is nil")
|
||||
errNilNextLink = errors.New("There are no more pages in this query results")
|
||||
)
|
||||
|
||||
// Entity represents an entity inside an Azure table.
|
||||
type Entity struct {
|
||||
Table *Table
|
||||
PartitionKey string
|
||||
RowKey string
|
||||
TimeStamp time.Time
|
||||
OdataMetadata string
|
||||
OdataType string
|
||||
OdataID string
|
||||
OdataEtag string
|
||||
OdataEditLink string
|
||||
Properties map[string]interface{}
|
||||
}
|
||||
|
||||
// GetEntityReference returns an Entity object with the specified
|
||||
// partition key and row key.
|
||||
func (t *Table) GetEntityReference(partitionKey, rowKey string) *Entity {
|
||||
return &Entity{
|
||||
PartitionKey: partitionKey,
|
||||
RowKey: rowKey,
|
||||
Table: t,
|
||||
}
|
||||
}
|
||||
|
||||
// EntityOptions includes options for entity operations.
|
||||
type EntityOptions struct {
|
||||
Timeout uint
|
||||
RequestID string `header:"x-ms-client-request-id"`
|
||||
}
|
||||
|
||||
// GetEntityOptions includes options for a get entity operation
|
||||
type GetEntityOptions struct {
|
||||
Select []string
|
||||
RequestID string `header:"x-ms-client-request-id"`
|
||||
}
|
||||
|
||||
// Get gets the referenced entity. Which properties to get can be
|
||||
// specified using the select option.
|
||||
// See:
|
||||
// https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/query-entities
|
||||
// https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/querying-tables-and-entities
|
||||
func (e *Entity) Get(timeout uint, ml MetadataLevel, options *GetEntityOptions) error {
|
||||
if ml == EmptyPayload {
|
||||
return errEmptyPayload
|
||||
}
|
||||
// RowKey and PartitionKey could be lost if not included in the query
|
||||
// As those are the entity identifiers, it is best if they are not lost
|
||||
rk := e.RowKey
|
||||
pk := e.PartitionKey
|
||||
|
||||
query := url.Values{
|
||||
"timeout": {strconv.FormatUint(uint64(timeout), 10)},
|
||||
}
|
||||
headers := e.Table.tsc.client.getStandardHeaders()
|
||||
headers[headerAccept] = string(ml)
|
||||
|
||||
if options != nil {
|
||||
if len(options.Select) > 0 {
|
||||
query.Add("$select", strings.Join(options.Select, ","))
|
||||
}
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
|
||||
uri := e.Table.tsc.client.getEndpoint(tableServiceName, e.buildPath(), query)
|
||||
resp, err := e.Table.tsc.client.exec(http.MethodGet, uri, headers, nil, e.Table.tsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
|
||||
if err = checkRespCode(resp, []int{http.StatusOK}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
respBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(respBody, e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.PartitionKey = pk
|
||||
e.RowKey = rk
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Insert inserts the referenced entity in its table.
|
||||
// The function fails if there is an entity with the same
|
||||
// PartitionKey and RowKey in the table.
|
||||
// ml determines the level of detail of metadata in the operation response,
|
||||
// or no data at all.
|
||||
// See: https://docs.microsoft.com/rest/api/storageservices/fileservices/insert-entity
|
||||
func (e *Entity) Insert(ml MetadataLevel, options *EntityOptions) error {
|
||||
query, headers := options.getParameters()
|
||||
headers = mergeHeaders(headers, e.Table.tsc.client.getStandardHeaders())
|
||||
|
||||
body, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
headers = addBodyRelatedHeaders(headers, len(body))
|
||||
headers = addReturnContentHeaders(headers, ml)
|
||||
|
||||
uri := e.Table.tsc.client.getEndpoint(tableServiceName, e.Table.buildPath(), query)
|
||||
resp, err := e.Table.tsc.client.exec(http.MethodPost, uri, headers, bytes.NewReader(body), e.Table.tsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
|
||||
if ml != EmptyPayload {
|
||||
if err = checkRespCode(resp, []int{http.StatusCreated}); err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = e.UnmarshalJSON(data); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = checkRespCode(resp, []int{http.StatusNoContent}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update updates the contents of an entity. The function fails if there is no entity
|
||||
// with the same PartitionKey and RowKey in the table or if the ETag is different
|
||||
// than the one in Azure.
|
||||
// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/update-entity2
|
||||
func (e *Entity) Update(force bool, options *EntityOptions) error {
|
||||
return e.updateMerge(force, http.MethodPut, options)
|
||||
}
|
||||
|
||||
// Merge merges the contents of entity specified with PartitionKey and RowKey
|
||||
// with the content specified in Properties.
|
||||
// The function fails if there is no entity with the same PartitionKey and
|
||||
// RowKey in the table or if the ETag is different than the one in Azure.
|
||||
// Read more: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/merge-entity
|
||||
func (e *Entity) Merge(force bool, options *EntityOptions) error {
|
||||
return e.updateMerge(force, "MERGE", options)
|
||||
}
|
||||
|
||||
// Delete deletes the entity.
|
||||
// The function fails if there is no entity with the same PartitionKey and
|
||||
// RowKey in the table or if the ETag is different than the one in Azure.
|
||||
// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/delete-entity1
|
||||
func (e *Entity) Delete(force bool, options *EntityOptions) error {
|
||||
query, headers := options.getParameters()
|
||||
headers = mergeHeaders(headers, e.Table.tsc.client.getStandardHeaders())
|
||||
|
||||
headers = addIfMatchHeader(headers, force, e.OdataEtag)
|
||||
headers = addReturnContentHeaders(headers, EmptyPayload)
|
||||
|
||||
uri := e.Table.tsc.client.getEndpoint(tableServiceName, e.buildPath(), query)
|
||||
resp, err := e.Table.tsc.client.exec(http.MethodDelete, uri, headers, nil, e.Table.tsc.auth)
|
||||
if err != nil {
|
||||
if resp.StatusCode == http.StatusPreconditionFailed {
|
||||
return fmt.Errorf(etagErrorTemplate, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
|
||||
if err = checkRespCode(resp, []int{http.StatusNoContent}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return e.updateTimestamp(resp.Header)
|
||||
}
|
||||
|
||||
// InsertOrReplace inserts an entity or replaces the existing one.
|
||||
// Read more: https://docs.microsoft.com/rest/api/storageservices/fileservices/insert-or-replace-entity
|
||||
func (e *Entity) InsertOrReplace(options *EntityOptions) error {
|
||||
return e.insertOr(http.MethodPut, options)
|
||||
}
|
||||
|
||||
// InsertOrMerge inserts an entity or merges the existing one.
|
||||
// Read more: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/insert-or-merge-entity
|
||||
func (e *Entity) InsertOrMerge(options *EntityOptions) error {
|
||||
return e.insertOr("MERGE", options)
|
||||
}
|
||||
|
||||
func (e *Entity) buildPath() string {
|
||||
return fmt.Sprintf("%s(PartitionKey='%s', RowKey='%s')", e.Table.buildPath(), e.PartitionKey, e.RowKey)
|
||||
}
|
||||
|
||||
// MarshalJSON is a custom marshaller for entity
|
||||
func (e *Entity) MarshalJSON() ([]byte, error) {
|
||||
completeMap := map[string]interface{}{}
|
||||
completeMap[partitionKeyNode] = e.PartitionKey
|
||||
completeMap[rowKeyNode] = e.RowKey
|
||||
for k, v := range e.Properties {
|
||||
typeKey := strings.Join([]string{k, OdataTypeSuffix}, "")
|
||||
switch t := v.(type) {
|
||||
case []byte:
|
||||
completeMap[typeKey] = OdataBinary
|
||||
completeMap[k] = t
|
||||
case time.Time:
|
||||
completeMap[typeKey] = OdataDateTime
|
||||
completeMap[k] = t.Format(time.RFC3339Nano)
|
||||
case uuid.UUID:
|
||||
completeMap[typeKey] = OdataGUID
|
||||
completeMap[k] = t.String()
|
||||
case int64:
|
||||
completeMap[typeKey] = OdataInt64
|
||||
completeMap[k] = fmt.Sprintf("%v", v)
|
||||
case float32, float64:
|
||||
completeMap[typeKey] = OdataDouble
|
||||
completeMap[k] = fmt.Sprintf("%v", v)
|
||||
default:
|
||||
completeMap[k] = v
|
||||
}
|
||||
if strings.HasSuffix(k, OdataTypeSuffix) {
|
||||
if !(completeMap[k] == OdataBinary ||
|
||||
completeMap[k] == OdataDateTime ||
|
||||
completeMap[k] == OdataGUID ||
|
||||
completeMap[k] == OdataInt64 ||
|
||||
completeMap[k] == OdataDouble) {
|
||||
return nil, fmt.Errorf("Odata.type annotation %v value is not valid", k)
|
||||
}
|
||||
valueKey := strings.TrimSuffix(k, OdataTypeSuffix)
|
||||
if _, ok := completeMap[valueKey]; !ok {
|
||||
return nil, fmt.Errorf("Odata.type annotation %v defined without value defined", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
return json.Marshal(completeMap)
|
||||
}
|
||||
|
||||
// UnmarshalJSON is a custom unmarshaller for entities
|
||||
func (e *Entity) UnmarshalJSON(data []byte) error {
|
||||
errorTemplate := "Deserializing error: %v"
|
||||
|
||||
props := map[string]interface{}{}
|
||||
err := json.Unmarshal(data, &props)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// deselialize metadata
|
||||
e.OdataMetadata = stringFromMap(props, "odata.metadata")
|
||||
e.OdataType = stringFromMap(props, "odata.type")
|
||||
e.OdataID = stringFromMap(props, "odata.id")
|
||||
e.OdataEtag = stringFromMap(props, "odata.etag")
|
||||
e.OdataEditLink = stringFromMap(props, "odata.editLink")
|
||||
e.PartitionKey = stringFromMap(props, partitionKeyNode)
|
||||
e.RowKey = stringFromMap(props, rowKeyNode)
|
||||
|
||||
// deserialize timestamp
|
||||
timeStamp, ok := props["Timestamp"]
|
||||
if ok {
|
||||
str, ok := timeStamp.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf(errorTemplate, "Timestamp casting error")
|
||||
}
|
||||
t, err := time.Parse(time.RFC3339Nano, str)
|
||||
if err != nil {
|
||||
return fmt.Errorf(errorTemplate, err)
|
||||
}
|
||||
e.TimeStamp = t
|
||||
}
|
||||
delete(props, "Timestamp")
|
||||
delete(props, "Timestamp@odata.type")
|
||||
|
||||
// deserialize entity (user defined fields)
|
||||
for k, v := range props {
|
||||
if strings.HasSuffix(k, OdataTypeSuffix) {
|
||||
valueKey := strings.TrimSuffix(k, OdataTypeSuffix)
|
||||
str, ok := props[valueKey].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf(errorTemplate, fmt.Sprintf("%v casting error", v))
|
||||
}
|
||||
switch v {
|
||||
case OdataBinary:
|
||||
props[valueKey], err = base64.StdEncoding.DecodeString(str)
|
||||
if err != nil {
|
||||
return fmt.Errorf(errorTemplate, err)
|
||||
}
|
||||
case OdataDateTime:
|
||||
t, err := time.Parse("2006-01-02T15:04:05Z", str)
|
||||
if err != nil {
|
||||
return fmt.Errorf(errorTemplate, err)
|
||||
}
|
||||
props[valueKey] = t
|
||||
case OdataGUID:
|
||||
props[valueKey] = uuid.FromStringOrNil(str)
|
||||
case OdataInt64:
|
||||
i, err := strconv.ParseInt(str, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf(errorTemplate, err)
|
||||
}
|
||||
props[valueKey] = i
|
||||
case OdataDouble:
|
||||
f, err := strconv.ParseFloat(str, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf(errorTemplate, err)
|
||||
}
|
||||
props[valueKey] = f
|
||||
default:
|
||||
return fmt.Errorf(errorTemplate, fmt.Sprintf("%v is not supported", v))
|
||||
}
|
||||
delete(props, k)
|
||||
}
|
||||
}
|
||||
|
||||
e.Properties = props
|
||||
return nil
|
||||
}
|
||||
|
||||
func getAndDelete(props map[string]interface{}, key string) interface{} {
|
||||
if value, ok := props[key]; ok {
|
||||
delete(props, key)
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addIfMatchHeader(h map[string]string, force bool, etag string) map[string]string {
|
||||
if force {
|
||||
h[headerIfMatch] = "*"
|
||||
} else {
|
||||
h[headerIfMatch] = etag
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// updates Etag and timestamp
|
||||
func (e *Entity) updateEtagAndTimestamp(headers http.Header) error {
|
||||
e.OdataEtag = headers.Get(headerEtag)
|
||||
return e.updateTimestamp(headers)
|
||||
}
|
||||
|
||||
func (e *Entity) updateTimestamp(headers http.Header) error {
|
||||
str := headers.Get(headerDate)
|
||||
t, err := time.Parse(time.RFC1123, str)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Update timestamp error: %v", err)
|
||||
}
|
||||
e.TimeStamp = t
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Entity) insertOr(verb string, options *EntityOptions) error {
|
||||
query, headers := options.getParameters()
|
||||
headers = mergeHeaders(headers, e.Table.tsc.client.getStandardHeaders())
|
||||
|
||||
body, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
headers = addBodyRelatedHeaders(headers, len(body))
|
||||
headers = addReturnContentHeaders(headers, EmptyPayload)
|
||||
|
||||
uri := e.Table.tsc.client.getEndpoint(tableServiceName, e.buildPath(), query)
|
||||
resp, err := e.Table.tsc.client.exec(verb, uri, headers, bytes.NewReader(body), e.Table.tsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
|
||||
if err = checkRespCode(resp, []int{http.StatusNoContent}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return e.updateEtagAndTimestamp(resp.Header)
|
||||
}
|
||||
|
||||
func (e *Entity) updateMerge(force bool, verb string, options *EntityOptions) error {
|
||||
query, headers := options.getParameters()
|
||||
headers = mergeHeaders(headers, e.Table.tsc.client.getStandardHeaders())
|
||||
|
||||
body, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
headers = addBodyRelatedHeaders(headers, len(body))
|
||||
headers = addIfMatchHeader(headers, force, e.OdataEtag)
|
||||
headers = addReturnContentHeaders(headers, EmptyPayload)
|
||||
|
||||
uri := e.Table.tsc.client.getEndpoint(tableServiceName, e.buildPath(), query)
|
||||
resp, err := e.Table.tsc.client.exec(verb, uri, headers, bytes.NewReader(body), e.Table.tsc.auth)
|
||||
if err != nil {
|
||||
if resp.StatusCode == http.StatusPreconditionFailed {
|
||||
return fmt.Errorf(etagErrorTemplate, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
|
||||
if err = checkRespCode(resp, []int{http.StatusNoContent}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return e.updateEtagAndTimestamp(resp.Header)
|
||||
}
|
||||
|
||||
func stringFromMap(props map[string]interface{}, key string) string {
|
||||
value := getAndDelete(props, key)
|
||||
if value != nil {
|
||||
return value.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (options *EntityOptions) getParameters() (url.Values, map[string]string) {
|
||||
query := url.Values{}
|
||||
headers := map[string]string{}
|
||||
if options != nil {
|
||||
query = addTimeout(query, options.Timeout)
|
||||
headers = headersFromStruct(*options)
|
||||
}
|
||||
return query, headers
|
||||
}
|
|
@ -1,484 +0,0 @@
|
|||
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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const fourMB = uint64(4194304)
|
||||
const oneTB = uint64(1099511627776)
|
||||
|
||||
// Export maximum range and file sizes
|
||||
|
||||
// MaxRangeSize defines the maximum size in bytes for a file range.
|
||||
const MaxRangeSize = fourMB
|
||||
|
||||
// MaxFileSize defines the maximum size in bytes for a file.
|
||||
const MaxFileSize = oneTB
|
||||
|
||||
// File represents a file on a share.
|
||||
type File struct {
|
||||
fsc *FileServiceClient
|
||||
Metadata map[string]string
|
||||
Name string `xml:"Name"`
|
||||
parent *Directory
|
||||
Properties FileProperties `xml:"Properties"`
|
||||
share *Share
|
||||
FileCopyProperties FileCopyState
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
// FileProperties contains various properties of a file.
|
||||
type FileProperties struct {
|
||||
CacheControl string `header:"x-ms-cache-control"`
|
||||
Disposition string `header:"x-ms-content-disposition"`
|
||||
Encoding string `header:"x-ms-content-encoding"`
|
||||
Etag string
|
||||
Language string `header:"x-ms-content-language"`
|
||||
LastModified string
|
||||
Length uint64 `xml:"Content-Length" header:"x-ms-content-length"`
|
||||
MD5 string `header:"x-ms-content-md5"`
|
||||
Type string `header:"x-ms-content-type"`
|
||||
}
|
||||
|
||||
// FileCopyState contains various properties of a file copy operation.
|
||||
type FileCopyState struct {
|
||||
CompletionTime string
|
||||
ID string `header:"x-ms-copy-id"`
|
||||
Progress string
|
||||
Source string
|
||||
Status string `header:"x-ms-copy-status"`
|
||||
StatusDesc string
|
||||
}
|
||||
|
||||
// FileStream contains file data returned from a call to GetFile.
|
||||
type FileStream struct {
|
||||
Body io.ReadCloser
|
||||
ContentMD5 string
|
||||
}
|
||||
|
||||
// FileRequestOptions will be passed to misc file operations.
|
||||
// Currently just Timeout (in seconds) but could expand.
|
||||
type FileRequestOptions struct {
|
||||
Timeout uint // timeout duration in seconds.
|
||||
}
|
||||
|
||||
func prepareOptions(options *FileRequestOptions) url.Values {
|
||||
params := url.Values{}
|
||||
if options != nil {
|
||||
params = addTimeout(params, options.Timeout)
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
// FileRanges contains a list of file range information for a file.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/List-Ranges
|
||||
type FileRanges struct {
|
||||
ContentLength uint64
|
||||
LastModified string
|
||||
ETag string
|
||||
FileRanges []FileRange `xml:"Range"`
|
||||
}
|
||||
|
||||
// FileRange contains range information for a file.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/List-Ranges
|
||||
type FileRange struct {
|
||||
Start uint64 `xml:"Start"`
|
||||
End uint64 `xml:"End"`
|
||||
}
|
||||
|
||||
func (fr FileRange) String() string {
|
||||
return fmt.Sprintf("bytes=%d-%d", fr.Start, fr.End)
|
||||
}
|
||||
|
||||
// builds the complete file path for this file object
|
||||
func (f *File) buildPath() string {
|
||||
return f.parent.buildPath() + "/" + f.Name
|
||||
}
|
||||
|
||||
// ClearRange releases the specified range of space in a file.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Put-Range
|
||||
func (f *File) ClearRange(fileRange FileRange, options *FileRequestOptions) error {
|
||||
var timeout *uint
|
||||
if options != nil {
|
||||
timeout = &options.Timeout
|
||||
}
|
||||
headers, err := f.modifyRange(nil, fileRange, timeout, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.updateEtagAndLastModified(headers)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create creates a new file or replaces an existing one.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Create-File
|
||||
func (f *File) Create(maxSize uint64, options *FileRequestOptions) error {
|
||||
if maxSize > oneTB {
|
||||
return fmt.Errorf("max file size is 1TB")
|
||||
}
|
||||
params := prepareOptions(options)
|
||||
headers := headersFromStruct(f.Properties)
|
||||
headers["x-ms-content-length"] = strconv.FormatUint(maxSize, 10)
|
||||
headers["x-ms-type"] = "file"
|
||||
|
||||
outputHeaders, err := f.fsc.createResource(f.buildPath(), resourceFile, params, mergeMDIntoExtraHeaders(f.Metadata, headers), []int{http.StatusCreated})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.Properties.Length = maxSize
|
||||
f.updateEtagAndLastModified(outputHeaders)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyFile operation copied a file/blob from the sourceURL to the path provided.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/copy-file
|
||||
func (f *File) CopyFile(sourceURL string, options *FileRequestOptions) error {
|
||||
extraHeaders := map[string]string{
|
||||
"x-ms-type": "file",
|
||||
"x-ms-copy-source": sourceURL,
|
||||
}
|
||||
params := prepareOptions(options)
|
||||
|
||||
headers, err := f.fsc.createResource(f.buildPath(), resourceFile, params, mergeMDIntoExtraHeaders(f.Metadata, extraHeaders), []int{http.StatusAccepted})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.updateEtagAndLastModified(headers)
|
||||
f.FileCopyProperties.ID = headers.Get("X-Ms-Copy-Id")
|
||||
f.FileCopyProperties.Status = headers.Get("X-Ms-Copy-Status")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete immediately removes this file from the storage account.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Delete-File2
|
||||
func (f *File) Delete(options *FileRequestOptions) error {
|
||||
return f.fsc.deleteResource(f.buildPath(), resourceFile, options)
|
||||
}
|
||||
|
||||
// DeleteIfExists removes this file if it exists.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Delete-File2
|
||||
func (f *File) DeleteIfExists(options *FileRequestOptions) (bool, error) {
|
||||
resp, err := f.fsc.deleteResourceNoClose(f.buildPath(), resourceFile, 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
|
||||
}
|
||||
|
||||
// GetFileOptions includes options for a get file operation
|
||||
type GetFileOptions struct {
|
||||
Timeout uint
|
||||
GetContentMD5 bool
|
||||
}
|
||||
|
||||
// DownloadToStream operation downloads the file.
|
||||
//
|
||||
// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-file
|
||||
func (f *File) DownloadToStream(options *FileRequestOptions) (io.ReadCloser, error) {
|
||||
params := prepareOptions(options)
|
||||
resp, err := f.fsc.getResourceNoClose(f.buildPath(), compNone, resourceFile, params, http.MethodGet, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = checkRespCode(resp, []int{http.StatusOK}); err != nil {
|
||||
drainRespBody(resp)
|
||||
return nil, err
|
||||
}
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
// DownloadRangeToStream operation downloads the specified range of this file with optional MD5 hash.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-file
|
||||
func (f *File) DownloadRangeToStream(fileRange FileRange, options *GetFileOptions) (fs FileStream, err error) {
|
||||
extraHeaders := map[string]string{
|
||||
"Range": fileRange.String(),
|
||||
}
|
||||
params := url.Values{}
|
||||
if options != nil {
|
||||
if options.GetContentMD5 {
|
||||
if isRangeTooBig(fileRange) {
|
||||
return fs, fmt.Errorf("must specify a range less than or equal to 4MB when getContentMD5 is true")
|
||||
}
|
||||
extraHeaders["x-ms-range-get-content-md5"] = "true"
|
||||
}
|
||||
params = addTimeout(params, options.Timeout)
|
||||
}
|
||||
|
||||
resp, err := f.fsc.getResourceNoClose(f.buildPath(), compNone, resourceFile, params, http.MethodGet, extraHeaders)
|
||||
if err != nil {
|
||||
return fs, err
|
||||
}
|
||||
|
||||
if err = checkRespCode(resp, []int{http.StatusOK, http.StatusPartialContent}); err != nil {
|
||||
drainRespBody(resp)
|
||||
return fs, err
|
||||
}
|
||||
|
||||
fs.Body = resp.Body
|
||||
if options != nil && options.GetContentMD5 {
|
||||
fs.ContentMD5 = resp.Header.Get("Content-MD5")
|
||||
}
|
||||
return fs, nil
|
||||
}
|
||||
|
||||
// Exists returns true if this file exists.
|
||||
func (f *File) Exists() (bool, error) {
|
||||
exists, headers, err := f.fsc.resourceExists(f.buildPath(), resourceFile)
|
||||
if exists {
|
||||
f.updateEtagAndLastModified(headers)
|
||||
f.updateProperties(headers)
|
||||
}
|
||||
return exists, err
|
||||
}
|
||||
|
||||
// FetchAttributes updates metadata and properties for this file.
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-file-properties
|
||||
func (f *File) FetchAttributes(options *FileRequestOptions) error {
|
||||
params := prepareOptions(options)
|
||||
headers, err := f.fsc.getResourceHeaders(f.buildPath(), compNone, resourceFile, params, http.MethodHead)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.updateEtagAndLastModified(headers)
|
||||
f.updateProperties(headers)
|
||||
f.Metadata = getMetadataFromHeaders(headers)
|
||||
return nil
|
||||
}
|
||||
|
||||
// returns true if the range is larger than 4MB
|
||||
func isRangeTooBig(fileRange FileRange) bool {
|
||||
if fileRange.End-fileRange.Start > fourMB {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ListRangesOptions includes options for a list file ranges operation
|
||||
type ListRangesOptions struct {
|
||||
Timeout uint
|
||||
ListRange *FileRange
|
||||
}
|
||||
|
||||
// ListRanges returns the list of valid ranges for this file.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/List-Ranges
|
||||
func (f *File) ListRanges(options *ListRangesOptions) (*FileRanges, error) {
|
||||
params := url.Values{"comp": {"rangelist"}}
|
||||
|
||||
// add optional range to list
|
||||
var headers map[string]string
|
||||
if options != nil {
|
||||
params = addTimeout(params, options.Timeout)
|
||||
if options.ListRange != nil {
|
||||
headers = make(map[string]string)
|
||||
headers["Range"] = options.ListRange.String()
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := f.fsc.listContent(f.buildPath(), params, headers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
var cl uint64
|
||||
cl, err = strconv.ParseUint(resp.Header.Get("x-ms-content-length"), 10, 64)
|
||||
if err != nil {
|
||||
ioutil.ReadAll(resp.Body)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var out FileRanges
|
||||
out.ContentLength = cl
|
||||
out.ETag = resp.Header.Get("ETag")
|
||||
out.LastModified = resp.Header.Get("Last-Modified")
|
||||
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
return &out, err
|
||||
}
|
||||
|
||||
// modifies a range of bytes in this file
|
||||
func (f *File) modifyRange(bytes io.Reader, fileRange FileRange, timeout *uint, contentMD5 *string) (http.Header, error) {
|
||||
if err := f.fsc.checkForStorageEmulator(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fileRange.End < fileRange.Start {
|
||||
return nil, errors.New("the value for rangeEnd must be greater than or equal to rangeStart")
|
||||
}
|
||||
if bytes != nil && isRangeTooBig(fileRange) {
|
||||
return nil, errors.New("range cannot exceed 4MB in size")
|
||||
}
|
||||
|
||||
params := url.Values{"comp": {"range"}}
|
||||
if timeout != nil {
|
||||
params = addTimeout(params, *timeout)
|
||||
}
|
||||
|
||||
uri := f.fsc.client.getEndpoint(fileServiceName, f.buildPath(), params)
|
||||
|
||||
// default to clear
|
||||
write := "clear"
|
||||
cl := uint64(0)
|
||||
|
||||
// if bytes is not nil then this is an update operation
|
||||
if bytes != nil {
|
||||
write = "update"
|
||||
cl = (fileRange.End - fileRange.Start) + 1
|
||||
}
|
||||
|
||||
extraHeaders := map[string]string{
|
||||
"Content-Length": strconv.FormatUint(cl, 10),
|
||||
"Range": fileRange.String(),
|
||||
"x-ms-write": write,
|
||||
}
|
||||
|
||||
if contentMD5 != nil {
|
||||
extraHeaders["Content-MD5"] = *contentMD5
|
||||
}
|
||||
|
||||
headers := mergeHeaders(f.fsc.client.getStandardHeaders(), extraHeaders)
|
||||
resp, err := f.fsc.client.exec(http.MethodPut, uri, headers, bytes, f.fsc.auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
return resp.Header, checkRespCode(resp, []int{http.StatusCreated})
|
||||
}
|
||||
|
||||
// SetMetadata replaces the metadata for this file.
|
||||
//
|
||||
// Some keys may be converted to Camel-Case before sending. All keys
|
||||
// are returned in lower case by GetFileMetadata. 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-File-Metadata
|
||||
func (f *File) SetMetadata(options *FileRequestOptions) error {
|
||||
headers, err := f.fsc.setResourceHeaders(f.buildPath(), compMetadata, resourceFile, mergeMDIntoExtraHeaders(f.Metadata, nil), options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.updateEtagAndLastModified(headers)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetProperties sets system properties on this file.
|
||||
//
|
||||
// Some keys may be converted to Camel-Case before sending. All keys
|
||||
// are returned in lower case by SetFileProperties. 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-File-Properties
|
||||
func (f *File) SetProperties(options *FileRequestOptions) error {
|
||||
headers, err := f.fsc.setResourceHeaders(f.buildPath(), compProperties, resourceFile, headersFromStruct(f.Properties), options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.updateEtagAndLastModified(headers)
|
||||
return nil
|
||||
}
|
||||
|
||||
// updates Etag and last modified date
|
||||
func (f *File) updateEtagAndLastModified(headers http.Header) {
|
||||
f.Properties.Etag = headers.Get("Etag")
|
||||
f.Properties.LastModified = headers.Get("Last-Modified")
|
||||
}
|
||||
|
||||
// updates file properties from the specified HTTP header
|
||||
func (f *File) updateProperties(header http.Header) {
|
||||
size, err := strconv.ParseUint(header.Get("Content-Length"), 10, 64)
|
||||
if err == nil {
|
||||
f.Properties.Length = size
|
||||
}
|
||||
|
||||
f.updateEtagAndLastModified(header)
|
||||
f.Properties.CacheControl = header.Get("Cache-Control")
|
||||
f.Properties.Disposition = header.Get("Content-Disposition")
|
||||
f.Properties.Encoding = header.Get("Content-Encoding")
|
||||
f.Properties.Language = header.Get("Content-Language")
|
||||
f.Properties.MD5 = header.Get("Content-MD5")
|
||||
f.Properties.Type = header.Get("Content-Type")
|
||||
}
|
||||
|
||||
// URL gets the canonical URL to this file.
|
||||
// This method does not create a publicly accessible URL if the file
|
||||
// is private and this method does not check if the file exists.
|
||||
func (f *File) URL() string {
|
||||
return f.fsc.client.getEndpoint(fileServiceName, f.buildPath(), nil)
|
||||
}
|
||||
|
||||
// WriteRangeOptions includes options for a write file range operation
|
||||
type WriteRangeOptions struct {
|
||||
Timeout uint
|
||||
ContentMD5 string
|
||||
}
|
||||
|
||||
// WriteRange writes a range of bytes to this file with an optional MD5 hash of the content (inside
|
||||
// options parameter). Note that the length of bytes must match (rangeEnd - rangeStart) + 1 with
|
||||
// a maximum size of 4MB.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Put-Range
|
||||
func (f *File) WriteRange(bytes io.Reader, fileRange FileRange, options *WriteRangeOptions) error {
|
||||
if bytes == nil {
|
||||
return errors.New("bytes cannot be nil")
|
||||
}
|
||||
var timeout *uint
|
||||
var md5 *string
|
||||
if options != nil {
|
||||
timeout = &options.Timeout
|
||||
md5 = &options.ContentMD5
|
||||
}
|
||||
|
||||
headers, err := f.modifyRange(bytes, fileRange, timeout, md5)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// it's perfectly legal for multiple go routines to call WriteRange
|
||||
// on the same *File (e.g. concurrently writing non-overlapping ranges)
|
||||
// so we must take the file mutex before updating our properties.
|
||||
f.mutex.Lock()
|
||||
f.updateEtagAndLastModified(headers)
|
||||
f.mutex.Unlock()
|
||||
return nil
|
||||
}
|
|
@ -1,338 +0,0 @@
|
|||
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"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// FileServiceClient contains operations for Microsoft Azure File Service.
|
||||
type FileServiceClient struct {
|
||||
client Client
|
||||
auth authentication
|
||||
}
|
||||
|
||||
// ListSharesParameters defines the set of customizable parameters to make a
|
||||
// List Shares call.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/List-Shares
|
||||
type ListSharesParameters struct {
|
||||
Prefix string
|
||||
Marker string
|
||||
Include string
|
||||
MaxResults uint
|
||||
Timeout uint
|
||||
}
|
||||
|
||||
// ShareListResponse contains the response fields from
|
||||
// ListShares call.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/List-Shares
|
||||
type ShareListResponse struct {
|
||||
XMLName xml.Name `xml:"EnumerationResults"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Prefix string `xml:"Prefix"`
|
||||
Marker string `xml:"Marker"`
|
||||
NextMarker string `xml:"NextMarker"`
|
||||
MaxResults int64 `xml:"MaxResults"`
|
||||
Shares []Share `xml:"Shares>Share"`
|
||||
}
|
||||
|
||||
type compType string
|
||||
|
||||
const (
|
||||
compNone compType = ""
|
||||
compList compType = "list"
|
||||
compMetadata compType = "metadata"
|
||||
compProperties compType = "properties"
|
||||
compRangeList compType = "rangelist"
|
||||
)
|
||||
|
||||
func (ct compType) String() string {
|
||||
return string(ct)
|
||||
}
|
||||
|
||||
type resourceType string
|
||||
|
||||
const (
|
||||
resourceDirectory resourceType = "directory"
|
||||
resourceFile resourceType = ""
|
||||
resourceShare resourceType = "share"
|
||||
)
|
||||
|
||||
func (rt resourceType) String() string {
|
||||
return string(rt)
|
||||
}
|
||||
|
||||
func (p ListSharesParameters) getParameters() url.Values {
|
||||
out := url.Values{}
|
||||
|
||||
if p.Prefix != "" {
|
||||
out.Set("prefix", p.Prefix)
|
||||
}
|
||||
if p.Marker != "" {
|
||||
out.Set("marker", p.Marker)
|
||||
}
|
||||
if p.Include != "" {
|
||||
out.Set("include", p.Include)
|
||||
}
|
||||
if p.MaxResults != 0 {
|
||||
out.Set("maxresults", strconv.FormatUint(uint64(p.MaxResults), 10))
|
||||
}
|
||||
if p.Timeout != 0 {
|
||||
out.Set("timeout", strconv.FormatUint(uint64(p.Timeout), 10))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (p ListDirsAndFilesParameters) getParameters() url.Values {
|
||||
out := url.Values{}
|
||||
|
||||
if p.Prefix != "" {
|
||||
out.Set("prefix", p.Prefix)
|
||||
}
|
||||
if p.Marker != "" {
|
||||
out.Set("marker", p.Marker)
|
||||
}
|
||||
if p.MaxResults != 0 {
|
||||
out.Set("maxresults", strconv.FormatUint(uint64(p.MaxResults), 10))
|
||||
}
|
||||
out = addTimeout(out, p.Timeout)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// returns url.Values for the specified types
|
||||
func getURLInitValues(comp compType, res resourceType) url.Values {
|
||||
values := url.Values{}
|
||||
if comp != compNone {
|
||||
values.Set("comp", comp.String())
|
||||
}
|
||||
if res != resourceFile {
|
||||
values.Set("restype", res.String())
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// GetShareReference returns a Share object for the specified share name.
|
||||
func (f *FileServiceClient) GetShareReference(name string) *Share {
|
||||
return &Share{
|
||||
fsc: f,
|
||||
Name: name,
|
||||
Properties: ShareProperties{
|
||||
Quota: -1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ListShares returns the list of shares in a storage account along with
|
||||
// pagination token and other response details.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/list-shares
|
||||
func (f FileServiceClient) ListShares(params ListSharesParameters) (*ShareListResponse, error) {
|
||||
q := mergeParams(params.getParameters(), url.Values{"comp": {"list"}})
|
||||
|
||||
var out ShareListResponse
|
||||
resp, err := f.listContent("", q, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
|
||||
// assign our client to the newly created Share objects
|
||||
for i := range out.Shares {
|
||||
out.Shares[i].fsc = &f
|
||||
}
|
||||
return &out, err
|
||||
}
|
||||
|
||||
// GetServiceProperties gets the properties of your storage account's file service.
|
||||
// File service does not support logging
|
||||
// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-file-service-properties
|
||||
func (f *FileServiceClient) GetServiceProperties() (*ServiceProperties, error) {
|
||||
return f.client.getServiceProperties(fileServiceName, f.auth)
|
||||
}
|
||||
|
||||
// SetServiceProperties sets the properties of your storage account's file service.
|
||||
// File service does not support logging
|
||||
// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/set-file-service-properties
|
||||
func (f *FileServiceClient) SetServiceProperties(props ServiceProperties) error {
|
||||
return f.client.setServiceProperties(props, fileServiceName, f.auth)
|
||||
}
|
||||
|
||||
// retrieves directory or share content
|
||||
func (f FileServiceClient) listContent(path string, params url.Values, extraHeaders map[string]string) (*http.Response, error) {
|
||||
if err := f.checkForStorageEmulator(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uri := f.client.getEndpoint(fileServiceName, path, params)
|
||||
extraHeaders = f.client.protectUserAgent(extraHeaders)
|
||||
headers := mergeHeaders(f.client.getStandardHeaders(), extraHeaders)
|
||||
|
||||
resp, err := f.client.exec(http.MethodGet, uri, headers, nil, f.auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = checkRespCode(resp, []int{http.StatusOK}); err != nil {
|
||||
drainRespBody(resp)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// returns true if the specified resource exists
|
||||
func (f FileServiceClient) resourceExists(path string, res resourceType) (bool, http.Header, error) {
|
||||
if err := f.checkForStorageEmulator(); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
uri := f.client.getEndpoint(fileServiceName, path, getURLInitValues(compNone, res))
|
||||
headers := f.client.getStandardHeaders()
|
||||
|
||||
resp, err := f.client.exec(http.MethodHead, uri, headers, nil, f.auth)
|
||||
if resp != nil {
|
||||
defer drainRespBody(resp)
|
||||
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound {
|
||||
return resp.StatusCode == http.StatusOK, resp.Header, nil
|
||||
}
|
||||
}
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
// creates a resource depending on the specified resource type
|
||||
func (f FileServiceClient) createResource(path string, res resourceType, urlParams url.Values, extraHeaders map[string]string, expectedResponseCodes []int) (http.Header, error) {
|
||||
resp, err := f.createResourceNoClose(path, res, urlParams, extraHeaders)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
return resp.Header, checkRespCode(resp, expectedResponseCodes)
|
||||
}
|
||||
|
||||
// creates a resource depending on the specified resource type, doesn't close the response body
|
||||
func (f FileServiceClient) createResourceNoClose(path string, res resourceType, urlParams url.Values, extraHeaders map[string]string) (*http.Response, error) {
|
||||
if err := f.checkForStorageEmulator(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
values := getURLInitValues(compNone, res)
|
||||
combinedParams := mergeParams(values, urlParams)
|
||||
uri := f.client.getEndpoint(fileServiceName, path, combinedParams)
|
||||
extraHeaders = f.client.protectUserAgent(extraHeaders)
|
||||
headers := mergeHeaders(f.client.getStandardHeaders(), extraHeaders)
|
||||
|
||||
return f.client.exec(http.MethodPut, uri, headers, nil, f.auth)
|
||||
}
|
||||
|
||||
// returns HTTP header data for the specified directory or share
|
||||
func (f FileServiceClient) getResourceHeaders(path string, comp compType, res resourceType, params url.Values, verb string) (http.Header, error) {
|
||||
resp, err := f.getResourceNoClose(path, comp, res, params, verb, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
|
||||
if err = checkRespCode(resp, []int{http.StatusOK}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp.Header, nil
|
||||
}
|
||||
|
||||
// gets the specified resource, doesn't close the response body
|
||||
func (f FileServiceClient) getResourceNoClose(path string, comp compType, res resourceType, params url.Values, verb string, extraHeaders map[string]string) (*http.Response, error) {
|
||||
if err := f.checkForStorageEmulator(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params = mergeParams(params, getURLInitValues(comp, res))
|
||||
uri := f.client.getEndpoint(fileServiceName, path, params)
|
||||
headers := mergeHeaders(f.client.getStandardHeaders(), extraHeaders)
|
||||
|
||||
return f.client.exec(verb, uri, headers, nil, f.auth)
|
||||
}
|
||||
|
||||
// deletes the resource and returns the response
|
||||
func (f FileServiceClient) deleteResource(path string, res resourceType, options *FileRequestOptions) error {
|
||||
resp, err := f.deleteResourceNoClose(path, res, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
return checkRespCode(resp, []int{http.StatusAccepted})
|
||||
}
|
||||
|
||||
// deletes the resource and returns the response, doesn't close the response body
|
||||
func (f FileServiceClient) deleteResourceNoClose(path string, res resourceType, options *FileRequestOptions) (*http.Response, error) {
|
||||
if err := f.checkForStorageEmulator(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
values := mergeParams(getURLInitValues(compNone, res), prepareOptions(options))
|
||||
uri := f.client.getEndpoint(fileServiceName, path, values)
|
||||
return f.client.exec(http.MethodDelete, uri, f.client.getStandardHeaders(), nil, f.auth)
|
||||
}
|
||||
|
||||
// merges metadata into extraHeaders and returns extraHeaders
|
||||
func mergeMDIntoExtraHeaders(metadata, extraHeaders map[string]string) map[string]string {
|
||||
if metadata == nil && extraHeaders == nil {
|
||||
return nil
|
||||
}
|
||||
if extraHeaders == nil {
|
||||
extraHeaders = make(map[string]string)
|
||||
}
|
||||
for k, v := range metadata {
|
||||
extraHeaders[userDefinedMetadataHeaderPrefix+k] = v
|
||||
}
|
||||
return extraHeaders
|
||||
}
|
||||
|
||||
// sets extra header data for the specified resource
|
||||
func (f FileServiceClient) setResourceHeaders(path string, comp compType, res resourceType, extraHeaders map[string]string, options *FileRequestOptions) (http.Header, error) {
|
||||
if err := f.checkForStorageEmulator(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params := mergeParams(getURLInitValues(comp, res), prepareOptions(options))
|
||||
uri := f.client.getEndpoint(fileServiceName, path, params)
|
||||
extraHeaders = f.client.protectUserAgent(extraHeaders)
|
||||
headers := mergeHeaders(f.client.getStandardHeaders(), extraHeaders)
|
||||
|
||||
resp, err := f.client.exec(http.MethodPut, uri, headers, nil, f.auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
|
||||
return resp.Header, checkRespCode(resp, []int{http.StatusOK})
|
||||
}
|
||||
|
||||
//checkForStorageEmulator determines if the client is setup for use with
|
||||
//Azure Storage Emulator, and returns a relevant error
|
||||
func (f FileServiceClient) checkForStorageEmulator() error {
|
||||
if f.client.accountName == StorageEmulatorAccountName {
|
||||
return fmt.Errorf("Error: File service is not currently supported by Azure Storage Emulator")
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,201 +0,0 @@
|
|||
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 (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// lease constants.
|
||||
const (
|
||||
leaseHeaderPrefix = "x-ms-lease-"
|
||||
headerLeaseID = "x-ms-lease-id"
|
||||
leaseAction = "x-ms-lease-action"
|
||||
leaseBreakPeriod = "x-ms-lease-break-period"
|
||||
leaseDuration = "x-ms-lease-duration"
|
||||
leaseProposedID = "x-ms-proposed-lease-id"
|
||||
leaseTime = "x-ms-lease-time"
|
||||
|
||||
acquireLease = "acquire"
|
||||
renewLease = "renew"
|
||||
changeLease = "change"
|
||||
releaseLease = "release"
|
||||
breakLease = "break"
|
||||
)
|
||||
|
||||
// leasePut is common PUT code for the various acquire/release/break etc functions.
|
||||
func (b *Blob) leaseCommonPut(headers map[string]string, expectedStatus int, options *LeaseOptions) (http.Header, error) {
|
||||
params := url.Values{"comp": {"lease"}}
|
||||
|
||||
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 nil, err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
|
||||
if err := checkRespCode(resp, []int{expectedStatus}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp.Header, nil
|
||||
}
|
||||
|
||||
// LeaseOptions includes options for all operations regarding leasing blobs
|
||||
type LeaseOptions struct {
|
||||
Timeout uint
|
||||
Origin string `header:"Origin"`
|
||||
IfMatch string `header:"If-Match"`
|
||||
IfNoneMatch string `header:"If-None-Match"`
|
||||
IfModifiedSince *time.Time `header:"If-Modified-Since"`
|
||||
IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
|
||||
RequestID string `header:"x-ms-client-request-id"`
|
||||
}
|
||||
|
||||
// AcquireLease creates a lease for a blob
|
||||
// returns leaseID acquired
|
||||
// In API Versions starting on 2012-02-12, the minimum leaseTimeInSeconds is 15, the maximum
|
||||
// non-infinite leaseTimeInSeconds is 60. To specify an infinite lease, provide the value -1.
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Lease-Blob
|
||||
func (b *Blob) AcquireLease(leaseTimeInSeconds int, proposedLeaseID string, options *LeaseOptions) (returnedLeaseID string, err error) {
|
||||
headers := b.Container.bsc.client.getStandardHeaders()
|
||||
headers[leaseAction] = acquireLease
|
||||
|
||||
if leaseTimeInSeconds == -1 {
|
||||
// Do nothing, but don't trigger the following clauses.
|
||||
} else if leaseTimeInSeconds > 60 || b.Container.bsc.client.apiVersion < "2012-02-12" {
|
||||
leaseTimeInSeconds = 60
|
||||
} else if leaseTimeInSeconds < 15 {
|
||||
leaseTimeInSeconds = 15
|
||||
}
|
||||
|
||||
headers[leaseDuration] = strconv.Itoa(leaseTimeInSeconds)
|
||||
|
||||
if proposedLeaseID != "" {
|
||||
headers[leaseProposedID] = proposedLeaseID
|
||||
}
|
||||
|
||||
respHeaders, err := b.leaseCommonPut(headers, http.StatusCreated, options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
returnedLeaseID = respHeaders.Get(http.CanonicalHeaderKey(headerLeaseID))
|
||||
|
||||
if returnedLeaseID != "" {
|
||||
return returnedLeaseID, nil
|
||||
}
|
||||
|
||||
return "", errors.New("LeaseID not returned")
|
||||
}
|
||||
|
||||
// BreakLease breaks the lease for a blob
|
||||
// Returns the timeout remaining in the lease in seconds
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Lease-Blob
|
||||
func (b *Blob) BreakLease(options *LeaseOptions) (breakTimeout int, err error) {
|
||||
headers := b.Container.bsc.client.getStandardHeaders()
|
||||
headers[leaseAction] = breakLease
|
||||
return b.breakLeaseCommon(headers, options)
|
||||
}
|
||||
|
||||
// BreakLeaseWithBreakPeriod breaks the lease for a blob
|
||||
// breakPeriodInSeconds is used to determine how long until new lease can be created.
|
||||
// Returns the timeout remaining in the lease in seconds
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Lease-Blob
|
||||
func (b *Blob) BreakLeaseWithBreakPeriod(breakPeriodInSeconds int, options *LeaseOptions) (breakTimeout int, err error) {
|
||||
headers := b.Container.bsc.client.getStandardHeaders()
|
||||
headers[leaseAction] = breakLease
|
||||
headers[leaseBreakPeriod] = strconv.Itoa(breakPeriodInSeconds)
|
||||
return b.breakLeaseCommon(headers, options)
|
||||
}
|
||||
|
||||
// breakLeaseCommon is common code for both version of BreakLease (with and without break period)
|
||||
func (b *Blob) breakLeaseCommon(headers map[string]string, options *LeaseOptions) (breakTimeout int, err error) {
|
||||
|
||||
respHeaders, err := b.leaseCommonPut(headers, http.StatusAccepted, options)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
breakTimeoutStr := respHeaders.Get(http.CanonicalHeaderKey(leaseTime))
|
||||
if breakTimeoutStr != "" {
|
||||
breakTimeout, err = strconv.Atoi(breakTimeoutStr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return breakTimeout, nil
|
||||
}
|
||||
|
||||
// ChangeLease changes a lease ID for a blob
|
||||
// Returns the new LeaseID acquired
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Lease-Blob
|
||||
func (b *Blob) ChangeLease(currentLeaseID string, proposedLeaseID string, options *LeaseOptions) (newLeaseID string, err error) {
|
||||
headers := b.Container.bsc.client.getStandardHeaders()
|
||||
headers[leaseAction] = changeLease
|
||||
headers[headerLeaseID] = currentLeaseID
|
||||
headers[leaseProposedID] = proposedLeaseID
|
||||
|
||||
respHeaders, err := b.leaseCommonPut(headers, http.StatusOK, options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
newLeaseID = respHeaders.Get(http.CanonicalHeaderKey(headerLeaseID))
|
||||
if newLeaseID != "" {
|
||||
return newLeaseID, nil
|
||||
}
|
||||
|
||||
return "", errors.New("LeaseID not returned")
|
||||
}
|
||||
|
||||
// ReleaseLease releases the lease for a blob
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Lease-Blob
|
||||
func (b *Blob) ReleaseLease(currentLeaseID string, options *LeaseOptions) error {
|
||||
headers := b.Container.bsc.client.getStandardHeaders()
|
||||
headers[leaseAction] = releaseLease
|
||||
headers[headerLeaseID] = currentLeaseID
|
||||
|
||||
_, err := b.leaseCommonPut(headers, http.StatusOK, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenewLease renews the lease for a blob as per https://msdn.microsoft.com/en-us/library/azure/ee691972.aspx
|
||||
func (b *Blob) RenewLease(currentLeaseID string, options *LeaseOptions) error {
|
||||
headers := b.Container.bsc.client.getStandardHeaders()
|
||||
headers[leaseAction] = renewLease
|
||||
headers[headerLeaseID] = currentLeaseID
|
||||
|
||||
_, err := b.leaseCommonPut(headers, http.StatusOK, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,171 +0,0 @@
|
|||
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"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Message represents an Azure message.
|
||||
type Message struct {
|
||||
Queue *Queue
|
||||
Text string `xml:"MessageText"`
|
||||
ID string `xml:"MessageId"`
|
||||
Insertion TimeRFC1123 `xml:"InsertionTime"`
|
||||
Expiration TimeRFC1123 `xml:"ExpirationTime"`
|
||||
PopReceipt string `xml:"PopReceipt"`
|
||||
NextVisible TimeRFC1123 `xml:"TimeNextVisible"`
|
||||
DequeueCount int `xml:"DequeueCount"`
|
||||
}
|
||||
|
||||
func (m *Message) buildPath() string {
|
||||
return fmt.Sprintf("%s/%s", m.Queue.buildPathMessages(), m.ID)
|
||||
}
|
||||
|
||||
// PutMessageOptions is the set of options can be specified for Put Messsage
|
||||
// operation. A zero struct does not use any preferences for the request.
|
||||
type PutMessageOptions struct {
|
||||
Timeout uint
|
||||
VisibilityTimeout int
|
||||
MessageTTL int
|
||||
RequestID string `header:"x-ms-client-request-id"`
|
||||
}
|
||||
|
||||
// Put operation adds a new message to the back of the message queue.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Put-Message
|
||||
func (m *Message) Put(options *PutMessageOptions) error {
|
||||
query := url.Values{}
|
||||
headers := m.Queue.qsc.client.getStandardHeaders()
|
||||
|
||||
req := putMessageRequest{MessageText: m.Text}
|
||||
body, nn, err := xmlMarshal(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
headers["Content-Length"] = strconv.Itoa(nn)
|
||||
|
||||
if options != nil {
|
||||
if options.VisibilityTimeout != 0 {
|
||||
query.Set("visibilitytimeout", strconv.Itoa(options.VisibilityTimeout))
|
||||
}
|
||||
if options.MessageTTL != 0 {
|
||||
query.Set("messagettl", strconv.Itoa(options.MessageTTL))
|
||||
}
|
||||
query = addTimeout(query, options.Timeout)
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
|
||||
uri := m.Queue.qsc.client.getEndpoint(queueServiceName, m.Queue.buildPathMessages(), query)
|
||||
resp, err := m.Queue.qsc.client.exec(http.MethodPost, uri, headers, body, m.Queue.qsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
err = checkRespCode(resp, []int{http.StatusCreated})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = xmlUnmarshal(resp.Body, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateMessageOptions is the set of options can be specified for Update Messsage
|
||||
// operation. A zero struct does not use any preferences for the request.
|
||||
type UpdateMessageOptions struct {
|
||||
Timeout uint
|
||||
VisibilityTimeout int
|
||||
RequestID string `header:"x-ms-client-request-id"`
|
||||
}
|
||||
|
||||
// Update operation updates the specified message.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Update-Message
|
||||
func (m *Message) Update(options *UpdateMessageOptions) error {
|
||||
query := url.Values{}
|
||||
if m.PopReceipt != "" {
|
||||
query.Set("popreceipt", m.PopReceipt)
|
||||
}
|
||||
|
||||
headers := m.Queue.qsc.client.getStandardHeaders()
|
||||
req := putMessageRequest{MessageText: m.Text}
|
||||
body, nn, err := xmlMarshal(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
headers["Content-Length"] = strconv.Itoa(nn)
|
||||
// visibilitytimeout is required for Update (zero or greater) so set the default here
|
||||
query.Set("visibilitytimeout", "0")
|
||||
if options != nil {
|
||||
if options.VisibilityTimeout != 0 {
|
||||
query.Set("visibilitytimeout", strconv.Itoa(options.VisibilityTimeout))
|
||||
}
|
||||
query = addTimeout(query, options.Timeout)
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
uri := m.Queue.qsc.client.getEndpoint(queueServiceName, m.buildPath(), query)
|
||||
|
||||
resp, err := m.Queue.qsc.client.exec(http.MethodPut, uri, headers, body, m.Queue.qsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
|
||||
m.PopReceipt = resp.Header.Get("x-ms-popreceipt")
|
||||
nextTimeStr := resp.Header.Get("x-ms-time-next-visible")
|
||||
if nextTimeStr != "" {
|
||||
nextTime, err := time.Parse(time.RFC1123, nextTimeStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.NextVisible = TimeRFC1123(nextTime)
|
||||
}
|
||||
|
||||
return checkRespCode(resp, []int{http.StatusNoContent})
|
||||
}
|
||||
|
||||
// Delete operation deletes the specified message.
|
||||
//
|
||||
// See https://msdn.microsoft.com/en-us/library/azure/dd179347.aspx
|
||||
func (m *Message) Delete(options *QueueServiceOptions) error {
|
||||
params := url.Values{"popreceipt": {m.PopReceipt}}
|
||||
headers := m.Queue.qsc.client.getStandardHeaders()
|
||||
|
||||
if options != nil {
|
||||
params = addTimeout(params, options.Timeout)
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
uri := m.Queue.qsc.client.getEndpoint(queueServiceName, m.buildPath(), params)
|
||||
|
||||
resp, err := m.Queue.qsc.client.exec(http.MethodDelete, uri, headers, nil, m.Queue.qsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
return checkRespCode(resp, []int{http.StatusNoContent})
|
||||
}
|
||||
|
||||
type putMessageRequest struct {
|
||||
XMLName xml.Name `xml:"QueueMessage"`
|
||||
MessageText string `xml:"MessageText"`
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
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.
|
||||
|
||||
// MetadataLevel determines if operations should return a paylod,
|
||||
// and it level of detail.
|
||||
type MetadataLevel string
|
||||
|
||||
// This consts are meant to help with Odata supported operations
|
||||
const (
|
||||
OdataTypeSuffix = "@odata.type"
|
||||
|
||||
// Types
|
||||
|
||||
OdataBinary = "Edm.Binary"
|
||||
OdataDateTime = "Edm.DateTime"
|
||||
OdataDouble = "Edm.Double"
|
||||
OdataGUID = "Edm.Guid"
|
||||
OdataInt64 = "Edm.Int64"
|
||||
|
||||
// Query options
|
||||
|
||||
OdataFilter = "$filter"
|
||||
OdataOrderBy = "$orderby"
|
||||
OdataTop = "$top"
|
||||
OdataSkip = "$skip"
|
||||
OdataCount = "$count"
|
||||
OdataExpand = "$expand"
|
||||
OdataSelect = "$select"
|
||||
OdataSearch = "$search"
|
||||
|
||||
EmptyPayload MetadataLevel = ""
|
||||
NoMetadata MetadataLevel = "application/json;odata=nometadata"
|
||||
MinimalMetadata MetadataLevel = "application/json;odata=minimalmetadata"
|
||||
FullMetadata MetadataLevel = "application/json;odata=fullmetadata"
|
||||
)
|
|
@ -1,203 +0,0 @@
|
|||
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"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetPageRangesResponse contains the response fields from
|
||||
// Get Page Ranges call.
|
||||
//
|
||||
// See https://msdn.microsoft.com/en-us/library/azure/ee691973.aspx
|
||||
type GetPageRangesResponse struct {
|
||||
XMLName xml.Name `xml:"PageList"`
|
||||
PageList []PageRange `xml:"PageRange"`
|
||||
}
|
||||
|
||||
// PageRange contains information about a page of a page blob from
|
||||
// Get Pages Range call.
|
||||
//
|
||||
// See https://msdn.microsoft.com/en-us/library/azure/ee691973.aspx
|
||||
type PageRange struct {
|
||||
Start int64 `xml:"Start"`
|
||||
End int64 `xml:"End"`
|
||||
}
|
||||
|
||||
var (
|
||||
errBlobCopyAborted = errors.New("storage: blob copy is aborted")
|
||||
errBlobCopyIDMismatch = errors.New("storage: blob copy id is a mismatch")
|
||||
)
|
||||
|
||||
// PutPageOptions includes the options for a put page operation
|
||||
type PutPageOptions struct {
|
||||
Timeout uint
|
||||
LeaseID string `header:"x-ms-lease-id"`
|
||||
IfSequenceNumberLessThanOrEqualTo *int `header:"x-ms-if-sequence-number-le"`
|
||||
IfSequenceNumberLessThan *int `header:"x-ms-if-sequence-number-lt"`
|
||||
IfSequenceNumberEqualTo *int `header:"x-ms-if-sequence-number-eq"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// WriteRange writes a range of pages to a page blob.
|
||||
// Ranges must be aligned with 512-byte boundaries and chunk must be of size
|
||||
// multiplies by 512.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Put-Page
|
||||
func (b *Blob) WriteRange(blobRange BlobRange, bytes io.Reader, options *PutPageOptions) error {
|
||||
if bytes == nil {
|
||||
return errors.New("bytes cannot be nil")
|
||||
}
|
||||
return b.modifyRange(blobRange, bytes, options)
|
||||
}
|
||||
|
||||
// ClearRange clears the given range in a page blob.
|
||||
// Ranges must be aligned with 512-byte boundaries and chunk must be of size
|
||||
// multiplies by 512.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Put-Page
|
||||
func (b *Blob) ClearRange(blobRange BlobRange, options *PutPageOptions) error {
|
||||
return b.modifyRange(blobRange, nil, options)
|
||||
}
|
||||
|
||||
func (b *Blob) modifyRange(blobRange BlobRange, bytes io.Reader, options *PutPageOptions) error {
|
||||
if blobRange.End < blobRange.Start {
|
||||
return errors.New("the value for rangeEnd must be greater than or equal to rangeStart")
|
||||
}
|
||||
if blobRange.Start%512 != 0 {
|
||||
return errors.New("the value for rangeStart must be a multiple of 512")
|
||||
}
|
||||
if blobRange.End%512 != 511 {
|
||||
return errors.New("the value for rangeEnd must be a multiple of 512 - 1")
|
||||
}
|
||||
|
||||
params := url.Values{"comp": {"page"}}
|
||||
|
||||
// default to clear
|
||||
write := "clear"
|
||||
var cl uint64
|
||||
|
||||
// if bytes is not nil then this is an update operation
|
||||
if bytes != nil {
|
||||
write = "update"
|
||||
cl = (blobRange.End - blobRange.Start) + 1
|
||||
}
|
||||
|
||||
headers := b.Container.bsc.client.getStandardHeaders()
|
||||
headers["x-ms-blob-type"] = string(BlobTypePage)
|
||||
headers["x-ms-page-write"] = write
|
||||
headers["x-ms-range"] = blobRange.String()
|
||||
headers["Content-Length"] = fmt.Sprintf("%v", cl)
|
||||
|
||||
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, bytes, b.Container.bsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
return checkRespCode(resp, []int{http.StatusCreated})
|
||||
}
|
||||
|
||||
// GetPageRangesOptions includes the options for a get page ranges operation
|
||||
type GetPageRangesOptions struct {
|
||||
Timeout uint
|
||||
Snapshot *time.Time
|
||||
PreviousSnapshot *time.Time
|
||||
Range *BlobRange
|
||||
LeaseID string `header:"x-ms-lease-id"`
|
||||
RequestID string `header:"x-ms-client-request-id"`
|
||||
}
|
||||
|
||||
// GetPageRanges returns the list of valid page ranges for a page blob.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Get-Page-Ranges
|
||||
func (b *Blob) GetPageRanges(options *GetPageRangesOptions) (GetPageRangesResponse, error) {
|
||||
params := url.Values{"comp": {"pagelist"}}
|
||||
headers := b.Container.bsc.client.getStandardHeaders()
|
||||
|
||||
if options != nil {
|
||||
params = addTimeout(params, options.Timeout)
|
||||
params = addSnapshot(params, options.Snapshot)
|
||||
if options.PreviousSnapshot != nil {
|
||||
params.Add("prevsnapshot", timeRFC3339Formatted(*options.PreviousSnapshot))
|
||||
}
|
||||
if options.Range != nil {
|
||||
headers["Range"] = options.Range.String()
|
||||
}
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
|
||||
|
||||
var out GetPageRangesResponse
|
||||
resp, err := b.Container.bsc.client.exec(http.MethodGet, uri, headers, nil, b.Container.bsc.auth)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
|
||||
if err = checkRespCode(resp, []int{http.StatusOK}); err != nil {
|
||||
return out, err
|
||||
}
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// PutPageBlob initializes an empty page blob with specified name and maximum
|
||||
// size in bytes (size must be aligned to a 512-byte boundary). A page blob must
|
||||
// be created using this method before writing pages.
|
||||
//
|
||||
// See CreateBlockBlobFromReader for more info on creating blobs.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Put-Blob
|
||||
func (b *Blob) PutPageBlob(options *PutBlobOptions) error {
|
||||
if b.Properties.ContentLength%512 != 0 {
|
||||
return errors.New("Content length must be aligned to a 512-byte boundary")
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
headers := b.Container.bsc.client.getStandardHeaders()
|
||||
headers["x-ms-blob-type"] = string(BlobTypePage)
|
||||
headers["x-ms-blob-content-length"] = fmt.Sprintf("%v", b.Properties.ContentLength)
|
||||
headers["x-ms-blob-sequence-number"] = fmt.Sprintf("%v", b.Properties.SequenceNumber)
|
||||
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, nil, b.Container.bsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.respondCreation(resp, BlobTypePage)
|
||||
}
|
|
@ -1,436 +0,0 @@
|
|||
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"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// casing is per Golang's http.Header canonicalizing the header names.
|
||||
approximateMessagesCountHeader = "X-Ms-Approximate-Messages-Count"
|
||||
)
|
||||
|
||||
// QueueAccessPolicy represents each access policy in the queue ACL.
|
||||
type QueueAccessPolicy struct {
|
||||
ID string
|
||||
StartTime time.Time
|
||||
ExpiryTime time.Time
|
||||
CanRead bool
|
||||
CanAdd bool
|
||||
CanUpdate bool
|
||||
CanProcess bool
|
||||
}
|
||||
|
||||
// QueuePermissions represents the queue ACLs.
|
||||
type QueuePermissions struct {
|
||||
AccessPolicies []QueueAccessPolicy
|
||||
}
|
||||
|
||||
// SetQueuePermissionOptions includes options for a set queue permissions operation
|
||||
type SetQueuePermissionOptions struct {
|
||||
Timeout uint
|
||||
RequestID string `header:"x-ms-client-request-id"`
|
||||
}
|
||||
|
||||
// Queue represents an Azure queue.
|
||||
type Queue struct {
|
||||
qsc *QueueServiceClient
|
||||
Name string
|
||||
Metadata map[string]string
|
||||
AproxMessageCount uint64
|
||||
}
|
||||
|
||||
func (q *Queue) buildPath() string {
|
||||
return fmt.Sprintf("/%s", q.Name)
|
||||
}
|
||||
|
||||
func (q *Queue) buildPathMessages() string {
|
||||
return fmt.Sprintf("%s/messages", q.buildPath())
|
||||
}
|
||||
|
||||
// QueueServiceOptions includes options for some queue service operations
|
||||
type QueueServiceOptions struct {
|
||||
Timeout uint
|
||||
RequestID string `header:"x-ms-client-request-id"`
|
||||
}
|
||||
|
||||
// Create operation creates a queue under the given account.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Create-Queue4
|
||||
func (q *Queue) Create(options *QueueServiceOptions) error {
|
||||
params := url.Values{}
|
||||
headers := q.qsc.client.getStandardHeaders()
|
||||
headers = q.qsc.client.addMetadataToHeaders(headers, q.Metadata)
|
||||
|
||||
if options != nil {
|
||||
params = addTimeout(params, options.Timeout)
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
uri := q.qsc.client.getEndpoint(queueServiceName, q.buildPath(), params)
|
||||
|
||||
resp, err := q.qsc.client.exec(http.MethodPut, uri, headers, nil, q.qsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
return checkRespCode(resp, []int{http.StatusCreated})
|
||||
}
|
||||
|
||||
// Delete operation permanently deletes the specified queue.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Delete-Queue3
|
||||
func (q *Queue) Delete(options *QueueServiceOptions) error {
|
||||
params := url.Values{}
|
||||
headers := q.qsc.client.getStandardHeaders()
|
||||
|
||||
if options != nil {
|
||||
params = addTimeout(params, options.Timeout)
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
uri := q.qsc.client.getEndpoint(queueServiceName, q.buildPath(), params)
|
||||
resp, err := q.qsc.client.exec(http.MethodDelete, uri, headers, nil, q.qsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
return checkRespCode(resp, []int{http.StatusNoContent})
|
||||
}
|
||||
|
||||
// Exists returns true if a queue with given name exists.
|
||||
func (q *Queue) Exists() (bool, error) {
|
||||
uri := q.qsc.client.getEndpoint(queueServiceName, q.buildPath(), url.Values{"comp": {"metadata"}})
|
||||
resp, err := q.qsc.client.exec(http.MethodGet, uri, q.qsc.client.getStandardHeaders(), nil, q.qsc.auth)
|
||||
if resp != nil {
|
||||
defer drainRespBody(resp)
|
||||
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound {
|
||||
return resp.StatusCode == http.StatusOK, nil
|
||||
}
|
||||
err = getErrorFromResponse(resp)
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
// SetMetadata operation sets user-defined metadata on the specified queue.
|
||||
// Metadata is associated with the queue as name-value pairs.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Set-Queue-Metadata
|
||||
func (q *Queue) SetMetadata(options *QueueServiceOptions) error {
|
||||
params := url.Values{"comp": {"metadata"}}
|
||||
headers := q.qsc.client.getStandardHeaders()
|
||||
headers = q.qsc.client.addMetadataToHeaders(headers, q.Metadata)
|
||||
|
||||
if options != nil {
|
||||
params = addTimeout(params, options.Timeout)
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
uri := q.qsc.client.getEndpoint(queueServiceName, q.buildPath(), params)
|
||||
|
||||
resp, err := q.qsc.client.exec(http.MethodPut, uri, headers, nil, q.qsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
return checkRespCode(resp, []int{http.StatusNoContent})
|
||||
}
|
||||
|
||||
// GetMetadata operation retrieves user-defined metadata and queue
|
||||
// properties on the specified queue. Metadata is associated with
|
||||
// the queue as name-values pairs.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Set-Queue-Metadata
|
||||
//
|
||||
// Because the way Golang's http client (and http.Header in particular)
|
||||
// canonicalize header names, the returned metadata names would always
|
||||
// be all lower case.
|
||||
func (q *Queue) GetMetadata(options *QueueServiceOptions) error {
|
||||
params := url.Values{"comp": {"metadata"}}
|
||||
headers := q.qsc.client.getStandardHeaders()
|
||||
|
||||
if options != nil {
|
||||
params = addTimeout(params, options.Timeout)
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
uri := q.qsc.client.getEndpoint(queueServiceName, q.buildPath(), params)
|
||||
|
||||
resp, err := q.qsc.client.exec(http.MethodGet, uri, headers, nil, q.qsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
|
||||
if err := checkRespCode(resp, []int{http.StatusOK}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aproxMessagesStr := resp.Header.Get(http.CanonicalHeaderKey(approximateMessagesCountHeader))
|
||||
if aproxMessagesStr != "" {
|
||||
aproxMessages, err := strconv.ParseUint(aproxMessagesStr, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.AproxMessageCount = aproxMessages
|
||||
}
|
||||
|
||||
q.Metadata = getMetadataFromHeaders(resp.Header)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMessageReference returns a message object with the specified text.
|
||||
func (q *Queue) GetMessageReference(text string) *Message {
|
||||
return &Message{
|
||||
Queue: q,
|
||||
Text: text,
|
||||
}
|
||||
}
|
||||
|
||||
// GetMessagesOptions is the set of options can be specified for Get
|
||||
// Messsages operation. A zero struct does not use any preferences for the
|
||||
// request.
|
||||
type GetMessagesOptions struct {
|
||||
Timeout uint
|
||||
NumOfMessages int
|
||||
VisibilityTimeout int
|
||||
RequestID string `header:"x-ms-client-request-id"`
|
||||
}
|
||||
|
||||
type messages struct {
|
||||
XMLName xml.Name `xml:"QueueMessagesList"`
|
||||
Messages []Message `xml:"QueueMessage"`
|
||||
}
|
||||
|
||||
// GetMessages operation retrieves one or more messages from the front of the
|
||||
// queue.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Get-Messages
|
||||
func (q *Queue) GetMessages(options *GetMessagesOptions) ([]Message, error) {
|
||||
query := url.Values{}
|
||||
headers := q.qsc.client.getStandardHeaders()
|
||||
|
||||
if options != nil {
|
||||
if options.NumOfMessages != 0 {
|
||||
query.Set("numofmessages", strconv.Itoa(options.NumOfMessages))
|
||||
}
|
||||
if options.VisibilityTimeout != 0 {
|
||||
query.Set("visibilitytimeout", strconv.Itoa(options.VisibilityTimeout))
|
||||
}
|
||||
query = addTimeout(query, options.Timeout)
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
uri := q.qsc.client.getEndpoint(queueServiceName, q.buildPathMessages(), query)
|
||||
|
||||
resp, err := q.qsc.client.exec(http.MethodGet, uri, headers, nil, q.qsc.auth)
|
||||
if err != nil {
|
||||
return []Message{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out messages
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
if err != nil {
|
||||
return []Message{}, err
|
||||
}
|
||||
for i := range out.Messages {
|
||||
out.Messages[i].Queue = q
|
||||
}
|
||||
return out.Messages, err
|
||||
}
|
||||
|
||||
// PeekMessagesOptions is the set of options can be specified for Peek
|
||||
// Messsage operation. A zero struct does not use any preferences for the
|
||||
// request.
|
||||
type PeekMessagesOptions struct {
|
||||
Timeout uint
|
||||
NumOfMessages int
|
||||
RequestID string `header:"x-ms-client-request-id"`
|
||||
}
|
||||
|
||||
// PeekMessages retrieves one or more messages from the front of the queue, but
|
||||
// does not alter the visibility of the message.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Peek-Messages
|
||||
func (q *Queue) PeekMessages(options *PeekMessagesOptions) ([]Message, error) {
|
||||
query := url.Values{"peekonly": {"true"}} // Required for peek operation
|
||||
headers := q.qsc.client.getStandardHeaders()
|
||||
|
||||
if options != nil {
|
||||
if options.NumOfMessages != 0 {
|
||||
query.Set("numofmessages", strconv.Itoa(options.NumOfMessages))
|
||||
}
|
||||
query = addTimeout(query, options.Timeout)
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
uri := q.qsc.client.getEndpoint(queueServiceName, q.buildPathMessages(), query)
|
||||
|
||||
resp, err := q.qsc.client.exec(http.MethodGet, uri, headers, nil, q.qsc.auth)
|
||||
if err != nil {
|
||||
return []Message{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out messages
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
if err != nil {
|
||||
return []Message{}, err
|
||||
}
|
||||
for i := range out.Messages {
|
||||
out.Messages[i].Queue = q
|
||||
}
|
||||
return out.Messages, err
|
||||
}
|
||||
|
||||
// ClearMessages operation deletes all messages from the specified queue.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Clear-Messages
|
||||
func (q *Queue) ClearMessages(options *QueueServiceOptions) error {
|
||||
params := url.Values{}
|
||||
headers := q.qsc.client.getStandardHeaders()
|
||||
|
||||
if options != nil {
|
||||
params = addTimeout(params, options.Timeout)
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
uri := q.qsc.client.getEndpoint(queueServiceName, q.buildPathMessages(), params)
|
||||
|
||||
resp, err := q.qsc.client.exec(http.MethodDelete, uri, headers, nil, q.qsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
return checkRespCode(resp, []int{http.StatusNoContent})
|
||||
}
|
||||
|
||||
// SetPermissions sets up queue permissions
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/set-queue-acl
|
||||
func (q *Queue) SetPermissions(permissions QueuePermissions, options *SetQueuePermissionOptions) error {
|
||||
body, length, err := generateQueueACLpayload(permissions.AccessPolicies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
params := url.Values{
|
||||
"comp": {"acl"},
|
||||
}
|
||||
headers := q.qsc.client.getStandardHeaders()
|
||||
headers["Content-Length"] = strconv.Itoa(length)
|
||||
|
||||
if options != nil {
|
||||
params = addTimeout(params, options.Timeout)
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
uri := q.qsc.client.getEndpoint(queueServiceName, q.buildPath(), params)
|
||||
resp, err := q.qsc.client.exec(http.MethodPut, uri, headers, body, q.qsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
return checkRespCode(resp, []int{http.StatusNoContent})
|
||||
}
|
||||
|
||||
func generateQueueACLpayload(policies []QueueAccessPolicy) (io.Reader, int, error) {
|
||||
sil := SignedIdentifiers{
|
||||
SignedIdentifiers: []SignedIdentifier{},
|
||||
}
|
||||
for _, qapd := range policies {
|
||||
permission := qapd.generateQueuePermissions()
|
||||
signedIdentifier := convertAccessPolicyToXMLStructs(qapd.ID, qapd.StartTime, qapd.ExpiryTime, permission)
|
||||
sil.SignedIdentifiers = append(sil.SignedIdentifiers, signedIdentifier)
|
||||
}
|
||||
return xmlMarshal(sil)
|
||||
}
|
||||
|
||||
func (qapd *QueueAccessPolicy) generateQueuePermissions() (permissions string) {
|
||||
// generate the permissions string (raup).
|
||||
// still want the end user API to have bool flags.
|
||||
permissions = ""
|
||||
|
||||
if qapd.CanRead {
|
||||
permissions += "r"
|
||||
}
|
||||
|
||||
if qapd.CanAdd {
|
||||
permissions += "a"
|
||||
}
|
||||
|
||||
if qapd.CanUpdate {
|
||||
permissions += "u"
|
||||
}
|
||||
|
||||
if qapd.CanProcess {
|
||||
permissions += "p"
|
||||
}
|
||||
|
||||
return permissions
|
||||
}
|
||||
|
||||
// GetQueuePermissionOptions includes options for a get queue permissions operation
|
||||
type GetQueuePermissionOptions struct {
|
||||
Timeout uint
|
||||
RequestID string `header:"x-ms-client-request-id"`
|
||||
}
|
||||
|
||||
// GetPermissions gets the queue permissions as per https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-queue-acl
|
||||
// If timeout is 0 then it will not be passed to Azure
|
||||
func (q *Queue) GetPermissions(options *GetQueuePermissionOptions) (*QueuePermissions, error) {
|
||||
params := url.Values{
|
||||
"comp": {"acl"},
|
||||
}
|
||||
headers := q.qsc.client.getStandardHeaders()
|
||||
|
||||
if options != nil {
|
||||
params = addTimeout(params, options.Timeout)
|
||||
headers = mergeHeaders(headers, headersFromStruct(*options))
|
||||
}
|
||||
uri := q.qsc.client.getEndpoint(queueServiceName, q.buildPath(), params)
|
||||
resp, err := q.qsc.client.exec(http.MethodGet, uri, headers, nil, q.qsc.auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var ap AccessPolicy
|
||||
err = xmlUnmarshal(resp.Body, &ap.SignedIdentifiersList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buildQueueAccessPolicy(ap, &resp.Header), nil
|
||||
}
|
||||
|
||||
func buildQueueAccessPolicy(ap AccessPolicy, headers *http.Header) *QueuePermissions {
|
||||
permissions := QueuePermissions{
|
||||
AccessPolicies: []QueueAccessPolicy{},
|
||||
}
|
||||
|
||||
for _, policy := range ap.SignedIdentifiersList.SignedIdentifiers {
|
||||
qapd := QueueAccessPolicy{
|
||||
ID: policy.ID,
|
||||
StartTime: policy.AccessPolicy.StartTime,
|
||||
ExpiryTime: policy.AccessPolicy.ExpiryTime,
|
||||
}
|
||||
qapd.CanRead = updatePermissions(policy.AccessPolicy.Permission, "r")
|
||||
qapd.CanAdd = updatePermissions(policy.AccessPolicy.Permission, "a")
|
||||
qapd.CanUpdate = updatePermissions(policy.AccessPolicy.Permission, "u")
|
||||
qapd.CanProcess = updatePermissions(policy.AccessPolicy.Permission, "p")
|
||||
|
||||
permissions.AccessPolicies = append(permissions.AccessPolicies, qapd)
|
||||
}
|
||||
return &permissions
|
||||
}
|
|
@ -1,146 +0,0 @@
|
|||
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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// QueueSASOptions are options to construct a blob SAS
|
||||
// URI.
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas
|
||||
type QueueSASOptions struct {
|
||||
QueueSASPermissions
|
||||
SASOptions
|
||||
}
|
||||
|
||||
// QueueSASPermissions includes the available permissions for
|
||||
// a queue SAS URI.
|
||||
type QueueSASPermissions struct {
|
||||
Read bool
|
||||
Add bool
|
||||
Update bool
|
||||
Process bool
|
||||
}
|
||||
|
||||
func (q QueueSASPermissions) buildString() string {
|
||||
permissions := ""
|
||||
|
||||
if q.Read {
|
||||
permissions += "r"
|
||||
}
|
||||
if q.Add {
|
||||
permissions += "a"
|
||||
}
|
||||
if q.Update {
|
||||
permissions += "u"
|
||||
}
|
||||
if q.Process {
|
||||
permissions += "p"
|
||||
}
|
||||
return permissions
|
||||
}
|
||||
|
||||
// GetSASURI creates an URL to the specified queue which contains the Shared
|
||||
// Access Signature with specified permissions and expiration time.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas
|
||||
func (q *Queue) GetSASURI(options QueueSASOptions) (string, error) {
|
||||
canonicalizedResource, err := q.qsc.client.buildCanonicalizedResource(q.buildPath(), q.qsc.auth, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// "The canonicalizedresouce portion of the string is a canonical path to the signed resource.
|
||||
// It must include the service name (blob, table, queue or file) for version 2015-02-21 or
|
||||
// later, the storage account name, and the resource name, and must be URL-decoded.
|
||||
// -- https://msdn.microsoft.com/en-us/library/azure/dn140255.aspx
|
||||
// We need to replace + with %2b first to avoid being treated as a space (which is correct for query strings, but not the path component).
|
||||
canonicalizedResource = strings.Replace(canonicalizedResource, "+", "%2b", -1)
|
||||
canonicalizedResource, err = url.QueryUnescape(canonicalizedResource)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
signedStart := ""
|
||||
if options.Start != (time.Time{}) {
|
||||
signedStart = options.Start.UTC().Format(time.RFC3339)
|
||||
}
|
||||
signedExpiry := options.Expiry.UTC().Format(time.RFC3339)
|
||||
|
||||
protocols := "https,http"
|
||||
if options.UseHTTPS {
|
||||
protocols = "https"
|
||||
}
|
||||
|
||||
permissions := options.QueueSASPermissions.buildString()
|
||||
stringToSign, err := queueSASStringToSign(q.qsc.client.apiVersion, canonicalizedResource, signedStart, signedExpiry, options.IP, permissions, protocols, options.Identifier)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
sig := q.qsc.client.computeHmac256(stringToSign)
|
||||
sasParams := url.Values{
|
||||
"sv": {q.qsc.client.apiVersion},
|
||||
"se": {signedExpiry},
|
||||
"sp": {permissions},
|
||||
"sig": {sig},
|
||||
}
|
||||
|
||||
if q.qsc.client.apiVersion >= "2015-04-05" {
|
||||
sasParams.Add("spr", protocols)
|
||||
addQueryParameter(sasParams, "sip", options.IP)
|
||||
}
|
||||
|
||||
uri := q.qsc.client.getEndpoint(queueServiceName, q.buildPath(), nil)
|
||||
sasURL, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sasURL.RawQuery = sasParams.Encode()
|
||||
return sasURL.String(), nil
|
||||
}
|
||||
|
||||
func queueSASStringToSign(signedVersion, canonicalizedResource, signedStart, signedExpiry, signedIP, signedPermissions, protocols, signedIdentifier string) (string, error) {
|
||||
|
||||
if signedVersion >= "2015-02-21" {
|
||||
canonicalizedResource = "/queue" + canonicalizedResource
|
||||
}
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/azure/dn140255.aspx#Anchor_12
|
||||
if signedVersion >= "2015-04-05" {
|
||||
return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s",
|
||||
signedPermissions,
|
||||
signedStart,
|
||||
signedExpiry,
|
||||
canonicalizedResource,
|
||||
signedIdentifier,
|
||||
signedIP,
|
||||
protocols,
|
||||
signedVersion), nil
|
||||
|
||||
}
|
||||
|
||||
// reference: http://msdn.microsoft.com/en-us/library/azure/dn140255.aspx
|
||||
if signedVersion >= "2013-08-15" {
|
||||
return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", signedPermissions, signedStart, signedExpiry, canonicalizedResource, signedIdentifier, signedVersion), nil
|
||||
}
|
||||
|
||||
return "", errors.New("storage: not implemented SAS for versions earlier than 2013-08-15")
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
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.
|
||||
|
||||
// QueueServiceClient contains operations for Microsoft Azure Queue Storage
|
||||
// Service.
|
||||
type QueueServiceClient struct {
|
||||
client Client
|
||||
auth authentication
|
||||
}
|
||||
|
||||
// GetServiceProperties gets the properties of your storage account's queue service.
|
||||
// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-queue-service-properties
|
||||
func (q *QueueServiceClient) GetServiceProperties() (*ServiceProperties, error) {
|
||||
return q.client.getServiceProperties(queueServiceName, q.auth)
|
||||
}
|
||||
|
||||
// SetServiceProperties sets the properties of your storage account's queue service.
|
||||
// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/set-queue-service-properties
|
||||
func (q *QueueServiceClient) SetServiceProperties(props ServiceProperties) error {
|
||||
return q.client.setServiceProperties(props, queueServiceName, q.auth)
|
||||
}
|
||||
|
||||
// GetQueueReference returns a Container object for the specified queue name.
|
||||
func (q *QueueServiceClient) GetQueueReference(name string) *Queue {
|
||||
return &Queue{
|
||||
qsc: q,
|
||||
Name: name,
|
||||
}
|
||||
}
|
|
@ -1,216 +0,0 @@
|
|||
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 (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Share represents an Azure file share.
|
||||
type Share struct {
|
||||
fsc *FileServiceClient
|
||||
Name string `xml:"Name"`
|
||||
Properties ShareProperties `xml:"Properties"`
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
// ShareProperties contains various properties of a share.
|
||||
type ShareProperties struct {
|
||||
LastModified string `xml:"Last-Modified"`
|
||||
Etag string `xml:"Etag"`
|
||||
Quota int `xml:"Quota"`
|
||||
}
|
||||
|
||||
// builds the complete path for this share object.
|
||||
func (s *Share) buildPath() string {
|
||||
return fmt.Sprintf("/%s", s.Name)
|
||||
}
|
||||
|
||||
// Create this share under the associated account.
|
||||
// If a share with the same name already exists, the operation fails.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Create-Share
|
||||
func (s *Share) Create(options *FileRequestOptions) error {
|
||||
extraheaders := map[string]string{}
|
||||
if s.Properties.Quota > 0 {
|
||||
extraheaders["x-ms-share-quota"] = strconv.Itoa(s.Properties.Quota)
|
||||
}
|
||||
|
||||
params := prepareOptions(options)
|
||||
headers, err := s.fsc.createResource(s.buildPath(), resourceShare, params, mergeMDIntoExtraHeaders(s.Metadata, extraheaders), []int{http.StatusCreated})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.updateEtagAndLastModified(headers)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateIfNotExists creates this share under the associated account if
|
||||
// it does not exist. Returns true if the share is newly created or false if
|
||||
// the share already exists.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Create-Share
|
||||
func (s *Share) CreateIfNotExists(options *FileRequestOptions) (bool, error) {
|
||||
extraheaders := map[string]string{}
|
||||
if s.Properties.Quota > 0 {
|
||||
extraheaders["x-ms-share-quota"] = strconv.Itoa(s.Properties.Quota)
|
||||
}
|
||||
|
||||
params := prepareOptions(options)
|
||||
resp, err := s.fsc.createResourceNoClose(s.buildPath(), resourceShare, params, extraheaders)
|
||||
if resp != nil {
|
||||
defer drainRespBody(resp)
|
||||
if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusConflict {
|
||||
if resp.StatusCode == http.StatusCreated {
|
||||
s.updateEtagAndLastModified(resp.Header)
|
||||
return true, nil
|
||||
}
|
||||
return false, s.FetchAttributes(nil)
|
||||
}
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Delete marks this share for deletion. The share along with any files
|
||||
// and directories contained within it are later deleted during garbage
|
||||
// collection. If the share does not exist the operation fails
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Delete-Share
|
||||
func (s *Share) Delete(options *FileRequestOptions) error {
|
||||
return s.fsc.deleteResource(s.buildPath(), resourceShare, options)
|
||||
}
|
||||
|
||||
// DeleteIfExists operation marks this share for deletion if it exists.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Delete-Share
|
||||
func (s *Share) DeleteIfExists(options *FileRequestOptions) (bool, error) {
|
||||
resp, err := s.fsc.deleteResourceNoClose(s.buildPath(), resourceShare, 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
|
||||
}
|
||||
|
||||
// Exists returns true if this share already exists
|
||||
// on the storage account, otherwise returns false.
|
||||
func (s *Share) Exists() (bool, error) {
|
||||
exists, headers, err := s.fsc.resourceExists(s.buildPath(), resourceShare)
|
||||
if exists {
|
||||
s.updateEtagAndLastModified(headers)
|
||||
s.updateQuota(headers)
|
||||
}
|
||||
return exists, err
|
||||
}
|
||||
|
||||
// FetchAttributes retrieves metadata and properties for this share.
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-share-properties
|
||||
func (s *Share) FetchAttributes(options *FileRequestOptions) error {
|
||||
params := prepareOptions(options)
|
||||
headers, err := s.fsc.getResourceHeaders(s.buildPath(), compNone, resourceShare, params, http.MethodHead)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.updateEtagAndLastModified(headers)
|
||||
s.updateQuota(headers)
|
||||
s.Metadata = getMetadataFromHeaders(headers)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRootDirectoryReference returns a Directory object at the root of this share.
|
||||
func (s *Share) GetRootDirectoryReference() *Directory {
|
||||
return &Directory{
|
||||
fsc: s.fsc,
|
||||
share: s,
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceClient returns the FileServiceClient associated with this share.
|
||||
func (s *Share) ServiceClient() *FileServiceClient {
|
||||
return s.fsc
|
||||
}
|
||||
|
||||
// SetMetadata replaces the metadata for this share.
|
||||
//
|
||||
// Some keys may be converted to Camel-Case before sending. All keys
|
||||
// are returned in lower case by GetShareMetadata. 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-share-metadata
|
||||
func (s *Share) SetMetadata(options *FileRequestOptions) error {
|
||||
headers, err := s.fsc.setResourceHeaders(s.buildPath(), compMetadata, resourceShare, mergeMDIntoExtraHeaders(s.Metadata, nil), options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.updateEtagAndLastModified(headers)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetProperties sets system properties for this share.
|
||||
//
|
||||
// Some keys may be converted to Camel-Case before sending. All keys
|
||||
// are returned in lower case by SetShareProperties. 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-Share-Properties
|
||||
func (s *Share) SetProperties(options *FileRequestOptions) error {
|
||||
extraheaders := map[string]string{}
|
||||
if s.Properties.Quota > 0 {
|
||||
if s.Properties.Quota > 5120 {
|
||||
return fmt.Errorf("invalid value %v for quota, valid values are [1, 5120]", s.Properties.Quota)
|
||||
}
|
||||
extraheaders["x-ms-share-quota"] = strconv.Itoa(s.Properties.Quota)
|
||||
}
|
||||
|
||||
headers, err := s.fsc.setResourceHeaders(s.buildPath(), compProperties, resourceShare, extraheaders, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.updateEtagAndLastModified(headers)
|
||||
return nil
|
||||
}
|
||||
|
||||
// updates Etag and last modified date
|
||||
func (s *Share) updateEtagAndLastModified(headers http.Header) {
|
||||
s.Properties.Etag = headers.Get("Etag")
|
||||
s.Properties.LastModified = headers.Get("Last-Modified")
|
||||
}
|
||||
|
||||
// updates quota value
|
||||
func (s *Share) updateQuota(headers http.Header) {
|
||||
quota, err := strconv.Atoi(headers.Get("x-ms-share-quota"))
|
||||
if err == nil {
|
||||
s.Properties.Quota = quota
|
||||
}
|
||||
}
|
||||
|
||||
// URL gets the canonical URL to this share. This method does not create a publicly accessible
|
||||
// URL if the share is private and this method does not check if the share exists.
|
||||
func (s *Share) URL() string {
|
||||
return s.fsc.client.getEndpoint(fileServiceName, s.buildPath(), url.Values{})
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
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 (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AccessPolicyDetailsXML has specifics about an access policy
|
||||
// annotated with XML details.
|
||||
type AccessPolicyDetailsXML struct {
|
||||
StartTime time.Time `xml:"Start"`
|
||||
ExpiryTime time.Time `xml:"Expiry"`
|
||||
Permission string `xml:"Permission"`
|
||||
}
|
||||
|
||||
// SignedIdentifier is a wrapper for a specific policy
|
||||
type SignedIdentifier struct {
|
||||
ID string `xml:"Id"`
|
||||
AccessPolicy AccessPolicyDetailsXML `xml:"AccessPolicy"`
|
||||
}
|
||||
|
||||
// SignedIdentifiers part of the response from GetPermissions call.
|
||||
type SignedIdentifiers struct {
|
||||
SignedIdentifiers []SignedIdentifier `xml:"SignedIdentifier"`
|
||||
}
|
||||
|
||||
// AccessPolicy is the response type from the GetPermissions call.
|
||||
type AccessPolicy struct {
|
||||
SignedIdentifiersList SignedIdentifiers `xml:"SignedIdentifiers"`
|
||||
}
|
||||
|
||||
// convertAccessPolicyToXMLStructs converts between AccessPolicyDetails which is a struct better for API usage to the
|
||||
// AccessPolicy struct which will get converted to XML.
|
||||
func convertAccessPolicyToXMLStructs(id string, startTime time.Time, expiryTime time.Time, permissions string) SignedIdentifier {
|
||||
return SignedIdentifier{
|
||||
ID: id,
|
||||
AccessPolicy: AccessPolicyDetailsXML{
|
||||
StartTime: startTime.UTC().Round(time.Second),
|
||||
ExpiryTime: expiryTime.UTC().Round(time.Second),
|
||||
Permission: permissions,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func updatePermissions(permissions, permission string) bool {
|
||||
return strings.Contains(permissions, permission)
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
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 (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ServiceProperties represents the storage account service properties
|
||||
type ServiceProperties struct {
|
||||
Logging *Logging
|
||||
HourMetrics *Metrics
|
||||
MinuteMetrics *Metrics
|
||||
Cors *Cors
|
||||
DeleteRetentionPolicy *RetentionPolicy // blob storage only
|
||||
StaticWebsite *StaticWebsite // blob storage only
|
||||
}
|
||||
|
||||
// Logging represents the Azure Analytics Logging settings
|
||||
type Logging struct {
|
||||
Version string
|
||||
Delete bool
|
||||
Read bool
|
||||
Write bool
|
||||
RetentionPolicy *RetentionPolicy
|
||||
}
|
||||
|
||||
// RetentionPolicy indicates if retention is enabled and for how many days
|
||||
type RetentionPolicy struct {
|
||||
Enabled bool
|
||||
Days *int
|
||||
}
|
||||
|
||||
// Metrics provide request statistics.
|
||||
type Metrics struct {
|
||||
Version string
|
||||
Enabled bool
|
||||
IncludeAPIs *bool
|
||||
RetentionPolicy *RetentionPolicy
|
||||
}
|
||||
|
||||
// Cors includes all the CORS rules
|
||||
type Cors struct {
|
||||
CorsRule []CorsRule
|
||||
}
|
||||
|
||||
// CorsRule includes all settings for a Cors rule
|
||||
type CorsRule struct {
|
||||
AllowedOrigins string
|
||||
AllowedMethods string
|
||||
MaxAgeInSeconds int
|
||||
ExposedHeaders string
|
||||
AllowedHeaders string
|
||||
}
|
||||
|
||||
// StaticWebsite - The properties that enable an account to host a static website
|
||||
type StaticWebsite struct {
|
||||
// Enabled - Indicates whether this account is hosting a static website
|
||||
Enabled bool
|
||||
// IndexDocument - The default name of the index page under each directory
|
||||
IndexDocument *string
|
||||
// ErrorDocument404Path - The absolute path of the custom 404 page
|
||||
ErrorDocument404Path *string
|
||||
}
|
||||
|
||||
func (c Client) getServiceProperties(service string, auth authentication) (*ServiceProperties, error) {
|
||||
query := url.Values{
|
||||
"restype": {"service"},
|
||||
"comp": {"properties"},
|
||||
}
|
||||
uri := c.getEndpoint(service, "", query)
|
||||
headers := c.getStandardHeaders()
|
||||
|
||||
resp, err := c.exec(http.MethodGet, uri, headers, nil, auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err := checkRespCode(resp, []int{http.StatusOK}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var out ServiceProperties
|
||||
err = xmlUnmarshal(resp.Body, &out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (c Client) setServiceProperties(props ServiceProperties, service string, auth authentication) error {
|
||||
query := url.Values{
|
||||
"restype": {"service"},
|
||||
"comp": {"properties"},
|
||||
}
|
||||
uri := c.getEndpoint(service, "", query)
|
||||
|
||||
// Ideally, StorageServiceProperties would be the output struct
|
||||
// This is to avoid golint stuttering, while generating the correct XML
|
||||
type StorageServiceProperties struct {
|
||||
Logging *Logging
|
||||
HourMetrics *Metrics
|
||||
MinuteMetrics *Metrics
|
||||
Cors *Cors
|
||||
DeleteRetentionPolicy *RetentionPolicy
|
||||
StaticWebsite *StaticWebsite
|
||||
}
|
||||
input := StorageServiceProperties{
|
||||
Logging: props.Logging,
|
||||
HourMetrics: props.HourMetrics,
|
||||
MinuteMetrics: props.MinuteMetrics,
|
||||
Cors: props.Cors,
|
||||
}
|
||||
// only set these fields for blob storage else it's invalid XML
|
||||
if service == blobServiceName {
|
||||
input.DeleteRetentionPolicy = props.DeleteRetentionPolicy
|
||||
input.StaticWebsite = props.StaticWebsite
|
||||
}
|
||||
|
||||
body, length, err := xmlMarshal(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
headers := c.getStandardHeaders()
|
||||
headers["Content-Length"] = strconv.Itoa(length)
|
||||
|
||||
resp, err := c.exec(http.MethodPut, uri, headers, body, auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
return checkRespCode(resp, []int{http.StatusAccepted})
|
||||
}
|
|
@ -1,423 +0,0 @@
|
|||
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 (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
tablesURIPath = "/Tables"
|
||||
nextTableQueryParameter = "NextTableName"
|
||||
headerNextPartitionKey = "x-ms-continuation-NextPartitionKey"
|
||||
headerNextRowKey = "x-ms-continuation-NextRowKey"
|
||||
nextPartitionKeyQueryParameter = "NextPartitionKey"
|
||||
nextRowKeyQueryParameter = "NextRowKey"
|
||||
)
|
||||
|
||||
// TableAccessPolicy are used for SETTING table policies
|
||||
type TableAccessPolicy struct {
|
||||
ID string
|
||||
StartTime time.Time
|
||||
ExpiryTime time.Time
|
||||
CanRead bool
|
||||
CanAppend bool
|
||||
CanUpdate bool
|
||||
CanDelete bool
|
||||
}
|
||||
|
||||
// Table represents an Azure table.
|
||||
type Table struct {
|
||||
tsc *TableServiceClient
|
||||
Name string `json:"TableName"`
|
||||
OdataEditLink string `json:"odata.editLink"`
|
||||
OdataID string `json:"odata.id"`
|
||||
OdataMetadata string `json:"odata.metadata"`
|
||||
OdataType string `json:"odata.type"`
|
||||
}
|
||||
|
||||
// EntityQueryResult contains the response from
|
||||
// ExecuteQuery and ExecuteQueryNextResults functions.
|
||||
type EntityQueryResult struct {
|
||||
OdataMetadata string `json:"odata.metadata"`
|
||||
Entities []*Entity `json:"value"`
|
||||
QueryNextLink
|
||||
table *Table
|
||||
}
|
||||
|
||||
type continuationToken struct {
|
||||
NextPartitionKey string
|
||||
NextRowKey string
|
||||
}
|
||||
|
||||
func (t *Table) buildPath() string {
|
||||
return fmt.Sprintf("/%s", t.Name)
|
||||
}
|
||||
|
||||
func (t *Table) buildSpecificPath() string {
|
||||
return fmt.Sprintf("%s('%s')", tablesURIPath, t.Name)
|
||||
}
|
||||
|
||||
// Get gets the referenced table.
|
||||
// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/querying-tables-and-entities
|
||||
func (t *Table) Get(timeout uint, ml MetadataLevel) error {
|
||||
if ml == EmptyPayload {
|
||||
return errEmptyPayload
|
||||
}
|
||||
|
||||
query := url.Values{
|
||||
"timeout": {strconv.FormatUint(uint64(timeout), 10)},
|
||||
}
|
||||
headers := t.tsc.client.getStandardHeaders()
|
||||
headers[headerAccept] = string(ml)
|
||||
|
||||
uri := t.tsc.client.getEndpoint(tableServiceName, t.buildSpecificPath(), query)
|
||||
resp, err := t.tsc.client.exec(http.MethodGet, uri, headers, nil, t.tsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err = checkRespCode(resp, []int{http.StatusOK}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
respBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(respBody, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create creates the referenced table.
|
||||
// This function fails if the name is not compliant
|
||||
// with the specification or the tables already exists.
|
||||
// ml determines the level of detail of metadata in the operation response,
|
||||
// or no data at all.
|
||||
// See https://docs.microsoft.com/rest/api/storageservices/fileservices/create-table
|
||||
func (t *Table) Create(timeout uint, ml MetadataLevel, options *TableOptions) error {
|
||||
uri := t.tsc.client.getEndpoint(tableServiceName, tablesURIPath, url.Values{
|
||||
"timeout": {strconv.FormatUint(uint64(timeout), 10)},
|
||||
})
|
||||
|
||||
type createTableRequest struct {
|
||||
TableName string `json:"TableName"`
|
||||
}
|
||||
req := createTableRequest{TableName: t.Name}
|
||||
buf := new(bytes.Buffer)
|
||||
if err := json.NewEncoder(buf).Encode(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
headers := t.tsc.client.getStandardHeaders()
|
||||
headers = addReturnContentHeaders(headers, ml)
|
||||
headers = addBodyRelatedHeaders(headers, buf.Len())
|
||||
headers = options.addToHeaders(headers)
|
||||
|
||||
resp, err := t.tsc.client.exec(http.MethodPost, uri, headers, buf, t.tsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if ml == EmptyPayload {
|
||||
if err := checkRespCode(resp, []int{http.StatusNoContent}); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := checkRespCode(resp, []int{http.StatusCreated}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if ml != EmptyPayload {
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(data, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes the referenced table.
|
||||
// This function fails if the table is not present.
|
||||
// Be advised: Delete deletes all the entries that may be present.
|
||||
// See https://docs.microsoft.com/rest/api/storageservices/fileservices/delete-table
|
||||
func (t *Table) Delete(timeout uint, options *TableOptions) error {
|
||||
uri := t.tsc.client.getEndpoint(tableServiceName, t.buildSpecificPath(), url.Values{
|
||||
"timeout": {strconv.Itoa(int(timeout))},
|
||||
})
|
||||
|
||||
headers := t.tsc.client.getStandardHeaders()
|
||||
headers = addReturnContentHeaders(headers, EmptyPayload)
|
||||
headers = options.addToHeaders(headers)
|
||||
|
||||
resp, err := t.tsc.client.exec(http.MethodDelete, uri, headers, nil, t.tsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
|
||||
return checkRespCode(resp, []int{http.StatusNoContent})
|
||||
}
|
||||
|
||||
// QueryOptions includes options for a query entities operation.
|
||||
// Top, filter and select are OData query options.
|
||||
type QueryOptions struct {
|
||||
Top uint
|
||||
Filter string
|
||||
Select []string
|
||||
RequestID string
|
||||
}
|
||||
|
||||
func (options *QueryOptions) getParameters() (url.Values, map[string]string) {
|
||||
query := url.Values{}
|
||||
headers := map[string]string{}
|
||||
if options != nil {
|
||||
if options.Top > 0 {
|
||||
query.Add(OdataTop, strconv.FormatUint(uint64(options.Top), 10))
|
||||
}
|
||||
if options.Filter != "" {
|
||||
query.Add(OdataFilter, options.Filter)
|
||||
}
|
||||
if len(options.Select) > 0 {
|
||||
query.Add(OdataSelect, strings.Join(options.Select, ","))
|
||||
}
|
||||
headers = addToHeaders(headers, "x-ms-client-request-id", options.RequestID)
|
||||
}
|
||||
return query, headers
|
||||
}
|
||||
|
||||
// QueryEntities returns the entities in the table.
|
||||
// You can use query options defined by the OData Protocol specification.
|
||||
//
|
||||
// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/query-entities
|
||||
func (t *Table) QueryEntities(timeout uint, ml MetadataLevel, options *QueryOptions) (*EntityQueryResult, error) {
|
||||
if ml == EmptyPayload {
|
||||
return nil, errEmptyPayload
|
||||
}
|
||||
query, headers := options.getParameters()
|
||||
query = addTimeout(query, timeout)
|
||||
uri := t.tsc.client.getEndpoint(tableServiceName, t.buildPath(), query)
|
||||
return t.queryEntities(uri, headers, ml)
|
||||
}
|
||||
|
||||
// NextResults returns the next page of results
|
||||
// from a QueryEntities or NextResults operation.
|
||||
//
|
||||
// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/query-entities
|
||||
// See https://docs.microsoft.com/rest/api/storageservices/fileservices/query-timeout-and-pagination
|
||||
func (eqr *EntityQueryResult) NextResults(options *TableOptions) (*EntityQueryResult, error) {
|
||||
if eqr == nil {
|
||||
return nil, errNilPreviousResult
|
||||
}
|
||||
if eqr.NextLink == nil {
|
||||
return nil, errNilNextLink
|
||||
}
|
||||
headers := options.addToHeaders(map[string]string{})
|
||||
return eqr.table.queryEntities(*eqr.NextLink, headers, eqr.ml)
|
||||
}
|
||||
|
||||
// SetPermissions sets up table ACL permissions
|
||||
// See https://docs.microsoft.com/rest/api/storageservices/fileservices/Set-Table-ACL
|
||||
func (t *Table) SetPermissions(tap []TableAccessPolicy, timeout uint, options *TableOptions) error {
|
||||
params := url.Values{"comp": {"acl"},
|
||||
"timeout": {strconv.Itoa(int(timeout))},
|
||||
}
|
||||
|
||||
uri := t.tsc.client.getEndpoint(tableServiceName, t.Name, params)
|
||||
headers := t.tsc.client.getStandardHeaders()
|
||||
headers = options.addToHeaders(headers)
|
||||
|
||||
body, length, err := generateTableACLPayload(tap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
headers["Content-Length"] = strconv.Itoa(length)
|
||||
|
||||
resp, err := t.tsc.client.exec(http.MethodPut, uri, headers, body, t.tsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp)
|
||||
|
||||
return checkRespCode(resp, []int{http.StatusNoContent})
|
||||
}
|
||||
|
||||
func generateTableACLPayload(policies []TableAccessPolicy) (io.Reader, int, error) {
|
||||
sil := SignedIdentifiers{
|
||||
SignedIdentifiers: []SignedIdentifier{},
|
||||
}
|
||||
for _, tap := range policies {
|
||||
permission := generateTablePermissions(&tap)
|
||||
signedIdentifier := convertAccessPolicyToXMLStructs(tap.ID, tap.StartTime, tap.ExpiryTime, permission)
|
||||
sil.SignedIdentifiers = append(sil.SignedIdentifiers, signedIdentifier)
|
||||
}
|
||||
return xmlMarshal(sil)
|
||||
}
|
||||
|
||||
// GetPermissions gets the table ACL permissions
|
||||
// See https://docs.microsoft.com/rest/api/storageservices/fileservices/get-table-acl
|
||||
func (t *Table) GetPermissions(timeout int, options *TableOptions) ([]TableAccessPolicy, error) {
|
||||
params := url.Values{"comp": {"acl"},
|
||||
"timeout": {strconv.Itoa(int(timeout))},
|
||||
}
|
||||
|
||||
uri := t.tsc.client.getEndpoint(tableServiceName, t.Name, params)
|
||||
headers := t.tsc.client.getStandardHeaders()
|
||||
headers = options.addToHeaders(headers)
|
||||
|
||||
resp, err := t.tsc.client.exec(http.MethodGet, uri, headers, nil, t.tsc.auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err = checkRespCode(resp, []int{http.StatusOK}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ap AccessPolicy
|
||||
err = xmlUnmarshal(resp.Body, &ap.SignedIdentifiersList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return updateTableAccessPolicy(ap), nil
|
||||
}
|
||||
|
||||
func (t *Table) queryEntities(uri string, headers map[string]string, ml MetadataLevel) (*EntityQueryResult, error) {
|
||||
headers = mergeHeaders(headers, t.tsc.client.getStandardHeaders())
|
||||
if ml != EmptyPayload {
|
||||
headers[headerAccept] = string(ml)
|
||||
}
|
||||
|
||||
resp, err := t.tsc.client.exec(http.MethodGet, uri, headers, nil, t.tsc.auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err = checkRespCode(resp, []int{http.StatusOK}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var entities EntityQueryResult
|
||||
err = json.Unmarshal(data, &entities)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range entities.Entities {
|
||||
entities.Entities[i].Table = t
|
||||
}
|
||||
entities.table = t
|
||||
|
||||
contToken := extractContinuationTokenFromHeaders(resp.Header)
|
||||
if contToken == nil {
|
||||
entities.NextLink = nil
|
||||
} else {
|
||||
originalURI, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := originalURI.Query()
|
||||
if contToken.NextPartitionKey != "" {
|
||||
v.Set(nextPartitionKeyQueryParameter, contToken.NextPartitionKey)
|
||||
}
|
||||
if contToken.NextRowKey != "" {
|
||||
v.Set(nextRowKeyQueryParameter, contToken.NextRowKey)
|
||||
}
|
||||
newURI := t.tsc.client.getEndpoint(tableServiceName, t.buildPath(), v)
|
||||
entities.NextLink = &newURI
|
||||
entities.ml = ml
|
||||
}
|
||||
|
||||
return &entities, nil
|
||||
}
|
||||
|
||||
func extractContinuationTokenFromHeaders(h http.Header) *continuationToken {
|
||||
ct := continuationToken{
|
||||
NextPartitionKey: h.Get(headerNextPartitionKey),
|
||||
NextRowKey: h.Get(headerNextRowKey),
|
||||
}
|
||||
|
||||
if ct.NextPartitionKey != "" || ct.NextRowKey != "" {
|
||||
return &ct
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateTableAccessPolicy(ap AccessPolicy) []TableAccessPolicy {
|
||||
taps := []TableAccessPolicy{}
|
||||
for _, policy := range ap.SignedIdentifiersList.SignedIdentifiers {
|
||||
tap := TableAccessPolicy{
|
||||
ID: policy.ID,
|
||||
StartTime: policy.AccessPolicy.StartTime,
|
||||
ExpiryTime: policy.AccessPolicy.ExpiryTime,
|
||||
}
|
||||
tap.CanRead = updatePermissions(policy.AccessPolicy.Permission, "r")
|
||||
tap.CanAppend = updatePermissions(policy.AccessPolicy.Permission, "a")
|
||||
tap.CanUpdate = updatePermissions(policy.AccessPolicy.Permission, "u")
|
||||
tap.CanDelete = updatePermissions(policy.AccessPolicy.Permission, "d")
|
||||
|
||||
taps = append(taps, tap)
|
||||
}
|
||||
return taps
|
||||
}
|
||||
|
||||
func generateTablePermissions(tap *TableAccessPolicy) (permissions string) {
|
||||
// generate the permissions string (raud).
|
||||
// still want the end user API to have bool flags.
|
||||
permissions = ""
|
||||
|
||||
if tap.CanRead {
|
||||
permissions += "r"
|
||||
}
|
||||
|
||||
if tap.CanAppend {
|
||||
permissions += "a"
|
||||
}
|
||||
|
||||
if tap.CanUpdate {
|
||||
permissions += "u"
|
||||
}
|
||||
|
||||
if tap.CanDelete {
|
||||
permissions += "d"
|
||||
}
|
||||
return permissions
|
||||
}
|
|
@ -1,325 +0,0 @@
|
|||
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 (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Operation type. Insert, Delete, Replace etc.
|
||||
type Operation int
|
||||
|
||||
// consts for batch operations.
|
||||
const (
|
||||
InsertOp = Operation(1)
|
||||
DeleteOp = Operation(2)
|
||||
ReplaceOp = Operation(3)
|
||||
MergeOp = Operation(4)
|
||||
InsertOrReplaceOp = Operation(5)
|
||||
InsertOrMergeOp = Operation(6)
|
||||
)
|
||||
|
||||
// BatchEntity used for tracking Entities to operate on and
|
||||
// whether operations (replace/merge etc) should be forced.
|
||||
// Wrapper for regular Entity with additional data specific for the entity.
|
||||
type BatchEntity struct {
|
||||
*Entity
|
||||
Force bool
|
||||
Op Operation
|
||||
}
|
||||
|
||||
// TableBatch stores all the entities that will be operated on during a batch process.
|
||||
// Entities can be inserted, replaced or deleted.
|
||||
type TableBatch struct {
|
||||
BatchEntitySlice []BatchEntity
|
||||
|
||||
// reference to table we're operating on.
|
||||
Table *Table
|
||||
}
|
||||
|
||||
// defaultChangesetHeaders for changeSets
|
||||
var defaultChangesetHeaders = map[string]string{
|
||||
"Accept": "application/json;odata=minimalmetadata",
|
||||
"Content-Type": "application/json",
|
||||
"Prefer": "return-no-content",
|
||||
}
|
||||
|
||||
// NewBatch return new TableBatch for populating.
|
||||
func (t *Table) NewBatch() *TableBatch {
|
||||
return &TableBatch{
|
||||
Table: t,
|
||||
}
|
||||
}
|
||||
|
||||
// InsertEntity adds an entity in preparation for a batch insert.
|
||||
func (t *TableBatch) InsertEntity(entity *Entity) {
|
||||
be := BatchEntity{Entity: entity, Force: false, Op: InsertOp}
|
||||
t.BatchEntitySlice = append(t.BatchEntitySlice, be)
|
||||
}
|
||||
|
||||
// InsertOrReplaceEntity adds an entity in preparation for a batch insert or replace.
|
||||
func (t *TableBatch) InsertOrReplaceEntity(entity *Entity, force bool) {
|
||||
be := BatchEntity{Entity: entity, Force: false, Op: InsertOrReplaceOp}
|
||||
t.BatchEntitySlice = append(t.BatchEntitySlice, be)
|
||||
}
|
||||
|
||||
// InsertOrReplaceEntityByForce adds an entity in preparation for a batch insert or replace. Forces regardless of ETag
|
||||
func (t *TableBatch) InsertOrReplaceEntityByForce(entity *Entity) {
|
||||
t.InsertOrReplaceEntity(entity, true)
|
||||
}
|
||||
|
||||
// InsertOrMergeEntity adds an entity in preparation for a batch insert or merge.
|
||||
func (t *TableBatch) InsertOrMergeEntity(entity *Entity, force bool) {
|
||||
be := BatchEntity{Entity: entity, Force: false, Op: InsertOrMergeOp}
|
||||
t.BatchEntitySlice = append(t.BatchEntitySlice, be)
|
||||
}
|
||||
|
||||
// InsertOrMergeEntityByForce adds an entity in preparation for a batch insert or merge. Forces regardless of ETag
|
||||
func (t *TableBatch) InsertOrMergeEntityByForce(entity *Entity) {
|
||||
t.InsertOrMergeEntity(entity, true)
|
||||
}
|
||||
|
||||
// ReplaceEntity adds an entity in preparation for a batch replace.
|
||||
func (t *TableBatch) ReplaceEntity(entity *Entity) {
|
||||
be := BatchEntity{Entity: entity, Force: false, Op: ReplaceOp}
|
||||
t.BatchEntitySlice = append(t.BatchEntitySlice, be)
|
||||
}
|
||||
|
||||
// DeleteEntity adds an entity in preparation for a batch delete
|
||||
func (t *TableBatch) DeleteEntity(entity *Entity, force bool) {
|
||||
be := BatchEntity{Entity: entity, Force: false, Op: DeleteOp}
|
||||
t.BatchEntitySlice = append(t.BatchEntitySlice, be)
|
||||
}
|
||||
|
||||
// DeleteEntityByForce adds an entity in preparation for a batch delete. Forces regardless of ETag
|
||||
func (t *TableBatch) DeleteEntityByForce(entity *Entity, force bool) {
|
||||
t.DeleteEntity(entity, true)
|
||||
}
|
||||
|
||||
// MergeEntity adds an entity in preparation for a batch merge
|
||||
func (t *TableBatch) MergeEntity(entity *Entity) {
|
||||
be := BatchEntity{Entity: entity, Force: false, Op: MergeOp}
|
||||
t.BatchEntitySlice = append(t.BatchEntitySlice, be)
|
||||
}
|
||||
|
||||
// ExecuteBatch executes many table operations in one request to Azure.
|
||||
// The operations can be combinations of Insert, Delete, Replace and Merge
|
||||
// Creates the inner changeset body (various operations, Insert, Delete etc) then creates the outer request packet that encompasses
|
||||
// the changesets.
|
||||
// As per document https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/performing-entity-group-transactions
|
||||
func (t *TableBatch) ExecuteBatch() error {
|
||||
|
||||
id, err := newUUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
changesetBoundary := fmt.Sprintf("changeset_%s", id.String())
|
||||
uri := t.Table.tsc.client.getEndpoint(tableServiceName, "$batch", nil)
|
||||
changesetBody, err := t.generateChangesetBody(changesetBoundary)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, err = newUUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
boundary := fmt.Sprintf("batch_%s", id.String())
|
||||
body, err := generateBody(changesetBody, changesetBoundary, boundary)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
headers := t.Table.tsc.client.getStandardHeaders()
|
||||
headers[headerContentType] = fmt.Sprintf("multipart/mixed; boundary=%s", boundary)
|
||||
|
||||
resp, err := t.Table.tsc.client.execBatchOperationJSON(http.MethodPost, uri, headers, bytes.NewReader(body.Bytes()), t.Table.tsc.auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drainRespBody(resp.resp)
|
||||
|
||||
if err = checkRespCode(resp.resp, []int{http.StatusAccepted}); err != nil {
|
||||
|
||||
// check which batch failed.
|
||||
operationFailedMessage := t.getFailedOperation(resp.odata.Err.Message.Value)
|
||||
requestID, date, version := getDebugHeaders(resp.resp.Header)
|
||||
return AzureStorageServiceError{
|
||||
StatusCode: resp.resp.StatusCode,
|
||||
Code: resp.odata.Err.Code,
|
||||
RequestID: requestID,
|
||||
Date: date,
|
||||
APIVersion: version,
|
||||
Message: operationFailedMessage,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getFailedOperation parses the original Azure error string and determines which operation failed
|
||||
// and generates appropriate message.
|
||||
func (t *TableBatch) getFailedOperation(errorMessage string) string {
|
||||
// errorMessage consists of "number:string" we just need the number.
|
||||
sp := strings.Split(errorMessage, ":")
|
||||
if len(sp) > 1 {
|
||||
msg := fmt.Sprintf("Element %s in the batch returned an unexpected response code.\n%s", sp[0], errorMessage)
|
||||
return msg
|
||||
}
|
||||
|
||||
// cant parse the message, just return the original message to client
|
||||
return errorMessage
|
||||
}
|
||||
|
||||
// generateBody generates the complete body for the batch request.
|
||||
func generateBody(changeSetBody *bytes.Buffer, changesetBoundary string, boundary string) (*bytes.Buffer, error) {
|
||||
|
||||
body := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(body)
|
||||
writer.SetBoundary(boundary)
|
||||
h := make(textproto.MIMEHeader)
|
||||
h.Set(headerContentType, fmt.Sprintf("multipart/mixed; boundary=%s\r\n", changesetBoundary))
|
||||
batchWriter, err := writer.CreatePart(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
batchWriter.Write(changeSetBody.Bytes())
|
||||
writer.Close()
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// generateChangesetBody generates the individual changesets for the various operations within the batch request.
|
||||
// There is a changeset for Insert, Delete, Merge etc.
|
||||
func (t *TableBatch) generateChangesetBody(changesetBoundary string) (*bytes.Buffer, error) {
|
||||
|
||||
body := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(body)
|
||||
writer.SetBoundary(changesetBoundary)
|
||||
|
||||
for _, be := range t.BatchEntitySlice {
|
||||
t.generateEntitySubset(&be, writer)
|
||||
}
|
||||
|
||||
writer.Close()
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// generateVerb generates the HTTP request VERB required for each changeset.
|
||||
func generateVerb(op Operation) (string, error) {
|
||||
switch op {
|
||||
case InsertOp:
|
||||
return http.MethodPost, nil
|
||||
case DeleteOp:
|
||||
return http.MethodDelete, nil
|
||||
case ReplaceOp, InsertOrReplaceOp:
|
||||
return http.MethodPut, nil
|
||||
case MergeOp, InsertOrMergeOp:
|
||||
return "MERGE", nil
|
||||
default:
|
||||
return "", errors.New("Unable to detect operation")
|
||||
}
|
||||
}
|
||||
|
||||
// generateQueryPath generates the query path for within the changesets
|
||||
// For inserts it will just be a table query path (table name)
|
||||
// but for other operations (modifying an existing entity) then
|
||||
// the partition/row keys need to be generated.
|
||||
func (t *TableBatch) generateQueryPath(op Operation, entity *Entity) string {
|
||||
if op == InsertOp {
|
||||
return entity.Table.buildPath()
|
||||
}
|
||||
return entity.buildPath()
|
||||
}
|
||||
|
||||
// generateGenericOperationHeaders generates common headers for a given operation.
|
||||
func generateGenericOperationHeaders(be *BatchEntity) map[string]string {
|
||||
retval := map[string]string{}
|
||||
|
||||
for k, v := range defaultChangesetHeaders {
|
||||
retval[k] = v
|
||||
}
|
||||
|
||||
if be.Op == DeleteOp || be.Op == ReplaceOp || be.Op == MergeOp {
|
||||
if be.Force || be.Entity.OdataEtag == "" {
|
||||
retval["If-Match"] = "*"
|
||||
} else {
|
||||
retval["If-Match"] = be.Entity.OdataEtag
|
||||
}
|
||||
}
|
||||
|
||||
return retval
|
||||
}
|
||||
|
||||
// generateEntitySubset generates body payload for particular batch entity
|
||||
func (t *TableBatch) generateEntitySubset(batchEntity *BatchEntity, writer *multipart.Writer) error {
|
||||
|
||||
h := make(textproto.MIMEHeader)
|
||||
h.Set(headerContentType, "application/http")
|
||||
h.Set(headerContentTransferEncoding, "binary")
|
||||
|
||||
verb, err := generateVerb(batchEntity.Op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genericOpHeadersMap := generateGenericOperationHeaders(batchEntity)
|
||||
queryPath := t.generateQueryPath(batchEntity.Op, batchEntity.Entity)
|
||||
uri := t.Table.tsc.client.getEndpoint(tableServiceName, queryPath, nil)
|
||||
|
||||
operationWriter, err := writer.CreatePart(h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
urlAndVerb := fmt.Sprintf("%s %s HTTP/1.1\r\n", verb, uri)
|
||||
operationWriter.Write([]byte(urlAndVerb))
|
||||
writeHeaders(genericOpHeadersMap, &operationWriter)
|
||||
operationWriter.Write([]byte("\r\n")) // additional \r\n is needed per changeset separating the "headers" and the body.
|
||||
|
||||
// delete operation doesn't need a body.
|
||||
if batchEntity.Op != DeleteOp {
|
||||
//var e Entity = batchEntity.Entity
|
||||
body, err := json.Marshal(batchEntity.Entity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
operationWriter.Write(body)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeHeaders(h map[string]string, writer *io.Writer) {
|
||||
// This way it is guaranteed the headers will be written in a sorted order
|
||||
var keys []string
|
||||
for k := range h {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
(*writer).Write([]byte(fmt.Sprintf("%s: %s\r\n", k, h[k])))
|
||||
}
|
||||
}
|
|
@ -1,204 +0,0 @@
|
|||
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/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
headerAccept = "Accept"
|
||||
headerEtag = "Etag"
|
||||
headerPrefer = "Prefer"
|
||||
headerXmsContinuation = "x-ms-Continuation-NextTableName"
|
||||
)
|
||||
|
||||
// TableServiceClient contains operations for Microsoft Azure Table Storage
|
||||
// Service.
|
||||
type TableServiceClient struct {
|
||||
client Client
|
||||
auth authentication
|
||||
}
|
||||
|
||||
// TableOptions includes options for some table operations
|
||||
type TableOptions struct {
|
||||
RequestID string
|
||||
}
|
||||
|
||||
func (options *TableOptions) addToHeaders(h map[string]string) map[string]string {
|
||||
if options != nil {
|
||||
h = addToHeaders(h, "x-ms-client-request-id", options.RequestID)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// QueryNextLink includes information for getting the next page of
|
||||
// results in query operations
|
||||
type QueryNextLink struct {
|
||||
NextLink *string
|
||||
ml MetadataLevel
|
||||
}
|
||||
|
||||
// GetServiceProperties gets the properties of your storage account's table service.
|
||||
// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-table-service-properties
|
||||
func (t *TableServiceClient) GetServiceProperties() (*ServiceProperties, error) {
|
||||
return t.client.getServiceProperties(tableServiceName, t.auth)
|
||||
}
|
||||
|
||||
// SetServiceProperties sets the properties of your storage account's table service.
|
||||
// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/set-table-service-properties
|
||||
func (t *TableServiceClient) SetServiceProperties(props ServiceProperties) error {
|
||||
return t.client.setServiceProperties(props, tableServiceName, t.auth)
|
||||
}
|
||||
|
||||
// GetTableReference returns a Table object for the specified table name.
|
||||
func (t *TableServiceClient) GetTableReference(name string) *Table {
|
||||
return &Table{
|
||||
tsc: t,
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// QueryTablesOptions includes options for some table operations
|
||||
type QueryTablesOptions struct {
|
||||
Top uint
|
||||
Filter string
|
||||
RequestID string
|
||||
}
|
||||
|
||||
func (options *QueryTablesOptions) getParameters() (url.Values, map[string]string) {
|
||||
query := url.Values{}
|
||||
headers := map[string]string{}
|
||||
if options != nil {
|
||||
if options.Top > 0 {
|
||||
query.Add(OdataTop, strconv.FormatUint(uint64(options.Top), 10))
|
||||
}
|
||||
if options.Filter != "" {
|
||||
query.Add(OdataFilter, options.Filter)
|
||||
}
|
||||
headers = addToHeaders(headers, "x-ms-client-request-id", options.RequestID)
|
||||
}
|
||||
return query, headers
|
||||
}
|
||||
|
||||
// QueryTables returns the tables in the storage account.
|
||||
// You can use query options defined by the OData Protocol specification.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/query-tables
|
||||
func (t *TableServiceClient) QueryTables(ml MetadataLevel, options *QueryTablesOptions) (*TableQueryResult, error) {
|
||||
query, headers := options.getParameters()
|
||||
uri := t.client.getEndpoint(tableServiceName, tablesURIPath, query)
|
||||
return t.queryTables(uri, headers, ml)
|
||||
}
|
||||
|
||||
// NextResults returns the next page of results
|
||||
// from a QueryTables or a NextResults operation.
|
||||
//
|
||||
// See https://docs.microsoft.com/rest/api/storageservices/fileservices/query-tables
|
||||
// See https://docs.microsoft.com/rest/api/storageservices/fileservices/query-timeout-and-pagination
|
||||
func (tqr *TableQueryResult) NextResults(options *TableOptions) (*TableQueryResult, error) {
|
||||
if tqr == nil {
|
||||
return nil, errNilPreviousResult
|
||||
}
|
||||
if tqr.NextLink == nil {
|
||||
return nil, errNilNextLink
|
||||
}
|
||||
headers := options.addToHeaders(map[string]string{})
|
||||
|
||||
return tqr.tsc.queryTables(*tqr.NextLink, headers, tqr.ml)
|
||||
}
|
||||
|
||||
// TableQueryResult contains the response from
|
||||
// QueryTables and QueryTablesNextResults functions.
|
||||
type TableQueryResult struct {
|
||||
OdataMetadata string `json:"odata.metadata"`
|
||||
Tables []Table `json:"value"`
|
||||
QueryNextLink
|
||||
tsc *TableServiceClient
|
||||
}
|
||||
|
||||
func (t *TableServiceClient) queryTables(uri string, headers map[string]string, ml MetadataLevel) (*TableQueryResult, error) {
|
||||
if ml == EmptyPayload {
|
||||
return nil, errEmptyPayload
|
||||
}
|
||||
headers = mergeHeaders(headers, t.client.getStandardHeaders())
|
||||
headers[headerAccept] = string(ml)
|
||||
|
||||
resp, err := t.client.exec(http.MethodGet, uri, headers, nil, t.auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err := checkRespCode(resp, []int{http.StatusOK}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
respBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out TableQueryResult
|
||||
err = json.Unmarshal(respBody, &out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range out.Tables {
|
||||
out.Tables[i].tsc = t
|
||||
}
|
||||
out.tsc = t
|
||||
|
||||
nextLink := resp.Header.Get(http.CanonicalHeaderKey(headerXmsContinuation))
|
||||
if nextLink == "" {
|
||||
out.NextLink = nil
|
||||
} else {
|
||||
originalURI, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := originalURI.Query()
|
||||
v.Set(nextTableQueryParameter, nextLink)
|
||||
newURI := t.client.getEndpoint(tableServiceName, tablesURIPath, v)
|
||||
out.NextLink = &newURI
|
||||
out.ml = ml
|
||||
}
|
||||
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func addBodyRelatedHeaders(h map[string]string, length int) map[string]string {
|
||||
h[headerContentType] = "application/json"
|
||||
h[headerContentLength] = fmt.Sprintf("%v", length)
|
||||
h[headerAcceptCharset] = "UTF-8"
|
||||
return h
|
||||
}
|
||||
|
||||
func addReturnContentHeaders(h map[string]string, ml MetadataLevel) map[string]string {
|
||||
if ml != EmptyPayload {
|
||||
h[headerPrefer] = "return-content"
|
||||
h[headerAccept] = string(ml)
|
||||
} else {
|
||||
h[headerPrefer] = "return-no-content"
|
||||
// From API version 2015-12-11 onwards, Accept header is required
|
||||
h[headerAccept] = string(NoMetadata)
|
||||
}
|
||||
return h
|
||||
}
|
|
@ -1,260 +0,0 @@
|
|||
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 (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
uuid "github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
fixedTime = time.Date(2050, time.December, 20, 21, 55, 0, 0, time.FixedZone("GMT", -6))
|
||||
accountSASOptions = AccountSASTokenOptions{
|
||||
Services: Services{
|
||||
Blob: true,
|
||||
},
|
||||
ResourceTypes: ResourceTypes{
|
||||
Service: true,
|
||||
Container: true,
|
||||
Object: true,
|
||||
},
|
||||
Permissions: Permissions{
|
||||
Read: true,
|
||||
Write: true,
|
||||
Delete: true,
|
||||
List: true,
|
||||
Add: true,
|
||||
Create: true,
|
||||
Update: true,
|
||||
Process: true,
|
||||
},
|
||||
Expiry: fixedTime,
|
||||
UseHTTPS: true,
|
||||
}
|
||||
)
|
||||
|
||||
func (c Client) computeHmac256(message string) string {
|
||||
h := hmac.New(sha256.New, c.accountKey)
|
||||
h.Write([]byte(message))
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func currentTimeRfc1123Formatted() string {
|
||||
return timeRfc1123Formatted(time.Now().UTC())
|
||||
}
|
||||
|
||||
func timeRfc1123Formatted(t time.Time) string {
|
||||
return t.Format(http.TimeFormat)
|
||||
}
|
||||
|
||||
func timeRFC3339Formatted(t time.Time) string {
|
||||
return t.Format("2006-01-02T15:04:05.0000000Z")
|
||||
}
|
||||
|
||||
func mergeParams(v1, v2 url.Values) url.Values {
|
||||
out := url.Values{}
|
||||
for k, v := range v1 {
|
||||
out[k] = v
|
||||
}
|
||||
for k, v := range v2 {
|
||||
vals, ok := out[k]
|
||||
if ok {
|
||||
vals = append(vals, v...)
|
||||
out[k] = vals
|
||||
} else {
|
||||
out[k] = v
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func prepareBlockListRequest(blocks []Block) string {
|
||||
s := `<?xml version="1.0" encoding="utf-8"?><BlockList>`
|
||||
for _, v := range blocks {
|
||||
s += fmt.Sprintf("<%s>%s</%s>", v.Status, v.ID, v.Status)
|
||||
}
|
||||
s += `</BlockList>`
|
||||
return s
|
||||
}
|
||||
|
||||
func xmlUnmarshal(body io.Reader, v interface{}) error {
|
||||
data, err := ioutil.ReadAll(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return xml.Unmarshal(data, v)
|
||||
}
|
||||
|
||||
func xmlMarshal(v interface{}) (io.Reader, int, error) {
|
||||
b, err := xml.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return bytes.NewReader(b), len(b), nil
|
||||
}
|
||||
|
||||
func headersFromStruct(v interface{}) map[string]string {
|
||||
headers := make(map[string]string)
|
||||
value := reflect.ValueOf(v)
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
key := value.Type().Field(i).Tag.Get("header")
|
||||
if key != "" {
|
||||
reflectedValue := reflect.Indirect(value.Field(i))
|
||||
var val string
|
||||
if reflectedValue.IsValid() {
|
||||
switch reflectedValue.Type() {
|
||||
case reflect.TypeOf(fixedTime):
|
||||
val = timeRfc1123Formatted(reflectedValue.Interface().(time.Time))
|
||||
case reflect.TypeOf(uint64(0)), reflect.TypeOf(uint(0)):
|
||||
val = strconv.FormatUint(reflectedValue.Uint(), 10)
|
||||
case reflect.TypeOf(int(0)):
|
||||
val = strconv.FormatInt(reflectedValue.Int(), 10)
|
||||
default:
|
||||
val = reflectedValue.String()
|
||||
}
|
||||
}
|
||||
if val != "" {
|
||||
headers[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
// merges extraHeaders into headers and returns headers
|
||||
func mergeHeaders(headers, extraHeaders map[string]string) map[string]string {
|
||||
for k, v := range extraHeaders {
|
||||
headers[k] = v
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
func addToHeaders(h map[string]string, key, value string) map[string]string {
|
||||
if value != "" {
|
||||
h[key] = value
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func addTimeToHeaders(h map[string]string, key string, value *time.Time) map[string]string {
|
||||
if value != nil {
|
||||
h = addToHeaders(h, key, timeRfc1123Formatted(*value))
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func addTimeout(params url.Values, timeout uint) url.Values {
|
||||
if timeout > 0 {
|
||||
params.Add("timeout", fmt.Sprintf("%v", timeout))
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
func addSnapshot(params url.Values, snapshot *time.Time) url.Values {
|
||||
if snapshot != nil {
|
||||
params.Add("snapshot", timeRFC3339Formatted(*snapshot))
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
func getTimeFromHeaders(h http.Header, key string) (*time.Time, error) {
|
||||
var out time.Time
|
||||
var err error
|
||||
outStr := h.Get(key)
|
||||
if outStr != "" {
|
||||
out, err = time.Parse(time.RFC1123, outStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// TimeRFC1123 is an alias for time.Time needed for custom Unmarshalling
|
||||
type TimeRFC1123 time.Time
|
||||
|
||||
// UnmarshalXML is a custom unmarshaller that overrides the default time unmarshal which uses a different time layout.
|
||||
func (t *TimeRFC1123) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
var value string
|
||||
d.DecodeElement(&value, &start)
|
||||
parse, err := time.Parse(time.RFC1123, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*t = TimeRFC1123(parse)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalXML marshals using time.RFC1123.
|
||||
func (t *TimeRFC1123) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
return e.EncodeElement(time.Time(*t).Format(time.RFC1123), start)
|
||||
}
|
||||
|
||||
// returns a map of custom metadata values from the specified HTTP header
|
||||
func getMetadataFromHeaders(header http.Header) map[string]string {
|
||||
metadata := make(map[string]string)
|
||||
for k, v := range header {
|
||||
// Can't trust CanonicalHeaderKey() to munge case
|
||||
// reliably. "_" is allowed in identifiers:
|
||||
// https://msdn.microsoft.com/en-us/library/azure/dd179414.aspx
|
||||
// https://msdn.microsoft.com/library/aa664670(VS.71).aspx
|
||||
// http://tools.ietf.org/html/rfc7230#section-3.2
|
||||
// ...but "_" is considered invalid by
|
||||
// CanonicalMIMEHeaderKey in
|
||||
// https://golang.org/src/net/textproto/reader.go?s=14615:14659#L542
|
||||
// so k can be "X-Ms-Meta-Lol" or "x-ms-meta-lol_rofl".
|
||||
k = strings.ToLower(k)
|
||||
if len(v) == 0 || !strings.HasPrefix(k, strings.ToLower(userDefinedMetadataHeaderPrefix)) {
|
||||
continue
|
||||
}
|
||||
// metadata["lol"] = content of the last X-Ms-Meta-Lol header
|
||||
k = k[len(userDefinedMetadataHeaderPrefix):]
|
||||
metadata[k] = v[len(v)-1]
|
||||
}
|
||||
|
||||
if len(metadata) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return metadata
|
||||
}
|
||||
|
||||
// newUUID returns a new uuid using RFC 4122 algorithm.
|
||||
func newUUID() (uuid.UUID, error) {
|
||||
u := [16]byte{}
|
||||
// Set all bits to randomly (or pseudo-randomly) chosen values.
|
||||
_, err := rand.Read(u[:])
|
||||
if err != nil {
|
||||
return uuid.UUID{}, err
|
||||
}
|
||||
u[8] = (u[8]&(0xff>>2) | (0x02 << 6)) // u.setVariant(ReservedRFC4122)
|
||||
u[6] = (u[6] & 0xF) | (uuid.V4 << 4) // u.setVersion(V4)
|
||||
return uuid.FromBytes(u[:])
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
65
vendor/github.com/Azure/azure-storage-blob-go/azblob/access_conditions.go
generated
vendored
Normal file
65
vendor/github.com/Azure/azure-storage-blob-go/azblob/access_conditions.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ModifiedAccessConditions identifies standard HTTP access conditions which you optionally set.
|
||||
type ModifiedAccessConditions struct {
|
||||
IfModifiedSince time.Time
|
||||
IfUnmodifiedSince time.Time
|
||||
IfMatch ETag
|
||||
IfNoneMatch ETag
|
||||
}
|
||||
|
||||
// pointers is for internal infrastructure. It returns the fields as pointers.
|
||||
func (ac ModifiedAccessConditions) pointers() (ims *time.Time, ius *time.Time, ime *ETag, inme *ETag) {
|
||||
if !ac.IfModifiedSince.IsZero() {
|
||||
ims = &ac.IfModifiedSince
|
||||
}
|
||||
if !ac.IfUnmodifiedSince.IsZero() {
|
||||
ius = &ac.IfUnmodifiedSince
|
||||
}
|
||||
if ac.IfMatch != ETagNone {
|
||||
ime = &ac.IfMatch
|
||||
}
|
||||
if ac.IfNoneMatch != ETagNone {
|
||||
inme = &ac.IfNoneMatch
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ContainerAccessConditions identifies container-specific access conditions which you optionally set.
|
||||
type ContainerAccessConditions struct {
|
||||
ModifiedAccessConditions
|
||||
LeaseAccessConditions
|
||||
}
|
||||
|
||||
// BlobAccessConditions identifies blob-specific access conditions which you optionally set.
|
||||
type BlobAccessConditions struct {
|
||||
ModifiedAccessConditions
|
||||
LeaseAccessConditions
|
||||
}
|
||||
|
||||
// LeaseAccessConditions identifies lease access conditions for a container or blob which you optionally set.
|
||||
type LeaseAccessConditions struct {
|
||||
LeaseID string
|
||||
}
|
||||
|
||||
// pointers is for internal infrastructure. It returns the fields as pointers.
|
||||
func (ac LeaseAccessConditions) pointers() (leaseID *string) {
|
||||
if ac.LeaseID != "" {
|
||||
leaseID = &ac.LeaseID
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
// getInt32 is for internal infrastructure. It is used with access condition values where
|
||||
// 0 (the default setting) is meaningful. The library interprets 0 as do not send the header
|
||||
// and the privately-storage field in the access condition object is stored as +1 higher than desired.
|
||||
// THis method returns true, if the value is > 0 (explicitly set) and the stored value - 1 (the set desired value).
|
||||
func getInt32(value int32) (bool, int32) {
|
||||
return value > 0, value - 1
|
||||
}
|
||||
*/
|
File diff suppressed because it is too large
Load Diff
238
vendor/github.com/Azure/azure-storage-blob-go/azblob/chunkwriting.go
generated
vendored
Normal file
238
vendor/github.com/Azure/azure-storage-blob-go/azblob/chunkwriting.go
generated
vendored
Normal file
|
@ -0,0 +1,238 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
guuid "github.com/google/uuid"
|
||||
)
|
||||
|
||||
// blockWriter provides methods to upload blocks that represent a file to a server and commit them.
|
||||
// This allows us to provide a local implementation that fakes the server for hermetic testing.
|
||||
type blockWriter interface {
|
||||
StageBlock(context.Context, string, io.ReadSeeker, LeaseAccessConditions, []byte) (*BlockBlobStageBlockResponse, error)
|
||||
CommitBlockList(context.Context, []string, BlobHTTPHeaders, Metadata, BlobAccessConditions) (*BlockBlobCommitBlockListResponse, error)
|
||||
}
|
||||
|
||||
// copyFromReader copies a source io.Reader to blob storage using concurrent uploads.
|
||||
// TODO(someone): The existing model provides a buffer size and buffer limit as limiting factors. The buffer size is probably
|
||||
// useless other than needing to be above some number, as the network stack is going to hack up the buffer over some size. The
|
||||
// max buffers is providing a cap on how much memory we use (by multiplying it times the buffer size) and how many go routines can upload
|
||||
// at a time. I think having a single max memory dial would be more efficient. We can choose an internal buffer size that works
|
||||
// well, 4 MiB or 8 MiB, and autoscale to as many goroutines within the memory limit. This gives a single dial to tweak and we can
|
||||
// choose a max value for the memory setting based on internal transfers within Azure (which will give us the maximum throughput model).
|
||||
// We can even provide a utility to dial this number in for customer networks to optimize their copies.
|
||||
func copyFromReader(ctx context.Context, from io.Reader, to blockWriter, o UploadStreamToBlockBlobOptions) (*BlockBlobCommitBlockListResponse, error) {
|
||||
o.defaults()
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
cp := &copier{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
reader: from,
|
||||
to: to,
|
||||
id: newID(),
|
||||
o: o,
|
||||
ch: make(chan copierChunk, 1),
|
||||
errCh: make(chan error, 1),
|
||||
buffers: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make([]byte, o.BufferSize)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Starts the pools of concurrent writers.
|
||||
cp.wg.Add(o.MaxBuffers)
|
||||
for i := 0; i < o.MaxBuffers; i++ {
|
||||
go cp.writer()
|
||||
}
|
||||
|
||||
// Send all our chunks until we get an error.
|
||||
var err error
|
||||
for {
|
||||
if err = cp.sendChunk(); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
// If the error is not EOF, then we have a problem.
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Close out our upload.
|
||||
if err := cp.close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cp.result, nil
|
||||
}
|
||||
|
||||
// copier streams a file via chunks in parallel from a reader representing a file.
|
||||
// Do not use directly, instead use copyFromReader().
|
||||
type copier struct {
|
||||
// ctx holds the context of a copier. This is normally a faux pas to store a Context in a struct. In this case,
|
||||
// the copier has the lifetime of a function call, so its fine.
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
// reader is the source to be written to storage.
|
||||
reader io.Reader
|
||||
// to is the location we are writing our chunks to.
|
||||
to blockWriter
|
||||
|
||||
id *id
|
||||
o UploadStreamToBlockBlobOptions
|
||||
|
||||
// num is the current chunk we are on.
|
||||
num int32
|
||||
// ch is used to pass the next chunk of data from our reader to one of the writers.
|
||||
ch chan copierChunk
|
||||
// errCh is used to hold the first error from our concurrent writers.
|
||||
errCh chan error
|
||||
// wg provides a count of how many writers we are waiting to finish.
|
||||
wg sync.WaitGroup
|
||||
// buffers provides a pool of chunks that can be reused.
|
||||
buffers sync.Pool
|
||||
|
||||
// result holds the final result from blob storage after we have submitted all chunks.
|
||||
result *BlockBlobCommitBlockListResponse
|
||||
}
|
||||
|
||||
type copierChunk struct {
|
||||
buffer []byte
|
||||
id string
|
||||
}
|
||||
|
||||
// getErr returns an error by priority. First, if a function set an error, it returns that error. Next, if the Context has an error
|
||||
// it returns that error. Otherwise it is nil. getErr supports only returning an error once per copier.
|
||||
func (c *copier) getErr() error {
|
||||
select {
|
||||
case err := <-c.errCh:
|
||||
return err
|
||||
default:
|
||||
}
|
||||
return c.ctx.Err()
|
||||
}
|
||||
|
||||
// sendChunk reads data from out internal reader, creates a chunk, and sends it to be written via a channel.
|
||||
// sendChunk returns io.EOF when the reader returns an io.EOF or io.ErrUnexpectedEOF.
|
||||
func (c *copier) sendChunk() error {
|
||||
if err := c.getErr(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buffer := c.buffers.Get().([]byte)
|
||||
n, err := io.ReadFull(c.reader, buffer)
|
||||
switch {
|
||||
case err == nil && n == 0:
|
||||
return nil
|
||||
case err == nil:
|
||||
c.ch <- copierChunk{
|
||||
buffer: buffer[0:n],
|
||||
id: c.id.next(),
|
||||
}
|
||||
return nil
|
||||
case err != nil && (err == io.EOF || err == io.ErrUnexpectedEOF) && n == 0:
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
c.ch <- copierChunk{
|
||||
buffer: buffer[0:n],
|
||||
id: c.id.next(),
|
||||
}
|
||||
return io.EOF
|
||||
}
|
||||
if err := c.getErr(); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// writer writes chunks sent on a channel.
|
||||
func (c *copier) writer() {
|
||||
defer c.wg.Done()
|
||||
|
||||
for chunk := range c.ch {
|
||||
if err := c.write(chunk); err != nil {
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
select {
|
||||
case c.errCh <- err:
|
||||
c.cancel()
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write uploads a chunk to blob storage.
|
||||
func (c *copier) write(chunk copierChunk) error {
|
||||
defer c.buffers.Put(chunk.buffer)
|
||||
|
||||
if err := c.ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := c.to.StageBlock(c.ctx, chunk.id, bytes.NewReader(chunk.buffer), LeaseAccessConditions{}, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write error: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// close commits our blocks to blob storage and closes our writer.
|
||||
func (c *copier) close() error {
|
||||
close(c.ch)
|
||||
c.wg.Wait()
|
||||
|
||||
if err := c.getErr(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
c.result, err = c.to.CommitBlockList(c.ctx, c.id.issued(), c.o.BlobHTTPHeaders, c.o.Metadata, c.o.AccessConditions)
|
||||
return err
|
||||
}
|
||||
|
||||
// id allows the creation of unique IDs based on UUID4 + an int32. This autoincrements.
|
||||
type id struct {
|
||||
u [64]byte
|
||||
num uint32
|
||||
all []string
|
||||
}
|
||||
|
||||
// newID constructs a new id.
|
||||
func newID() *id {
|
||||
uu := guuid.New()
|
||||
u := [64]byte{}
|
||||
copy(u[:], uu[:])
|
||||
return &id{u: u}
|
||||
}
|
||||
|
||||
// next returns the next ID. This is not thread-safe.
|
||||
func (id *id) next() string {
|
||||
defer func() { id.num++ }()
|
||||
|
||||
binary.BigEndian.PutUint32((id.u[len(guuid.UUID{}):]), id.num)
|
||||
str := base64.StdEncoding.EncodeToString(id.u[:])
|
||||
id.all = append(id.all, str)
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
// issued returns all ids that have been issued. This returned value shares the internal slice so it is not safe to modify the return.
|
||||
// The value is only valid until the next time next() is called.
|
||||
func (id *id) issued() []string {
|
||||
return id.all
|
||||
}
|
396
vendor/github.com/Azure/azure-storage-blob-go/azblob/highlevel.go
generated
vendored
Normal file
396
vendor/github.com/Azure/azure-storage-blob-go/azblob/highlevel.go
generated
vendored
Normal file
|
@ -0,0 +1,396 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"bytes"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
)
|
||||
|
||||
// CommonResponse returns the headers common to all blob REST API responses.
|
||||
type CommonResponse interface {
|
||||
// ETag returns the value for header ETag.
|
||||
ETag() ETag
|
||||
|
||||
// LastModified returns the value for header Last-Modified.
|
||||
LastModified() time.Time
|
||||
|
||||
// RequestID returns the value for header x-ms-request-id.
|
||||
RequestID() string
|
||||
|
||||
// Date returns the value for header Date.
|
||||
Date() time.Time
|
||||
|
||||
// Version returns the value for header x-ms-version.
|
||||
Version() string
|
||||
|
||||
// Response returns the raw HTTP response object.
|
||||
Response() *http.Response
|
||||
}
|
||||
|
||||
// UploadToBlockBlobOptions identifies options used by the UploadBufferToBlockBlob and UploadFileToBlockBlob functions.
|
||||
type UploadToBlockBlobOptions struct {
|
||||
// BlockSize specifies the block size to use; the default (and maximum size) is BlockBlobMaxStageBlockBytes.
|
||||
BlockSize int64
|
||||
|
||||
// Progress is a function that is invoked periodically as bytes are sent to the BlockBlobURL.
|
||||
// Note that the progress reporting is not always increasing; it can go down when retrying a request.
|
||||
Progress pipeline.ProgressReceiver
|
||||
|
||||
// BlobHTTPHeaders indicates the HTTP headers to be associated with the blob.
|
||||
BlobHTTPHeaders BlobHTTPHeaders
|
||||
|
||||
// Metadata indicates the metadata to be associated with the blob when PutBlockList is called.
|
||||
Metadata Metadata
|
||||
|
||||
// AccessConditions indicates the access conditions for the block blob.
|
||||
AccessConditions BlobAccessConditions
|
||||
|
||||
// Parallelism indicates the maximum number of blocks to upload in parallel (0=default)
|
||||
Parallelism uint16
|
||||
}
|
||||
|
||||
// UploadBufferToBlockBlob uploads a buffer in blocks to a block blob.
|
||||
func UploadBufferToBlockBlob(ctx context.Context, b []byte,
|
||||
blockBlobURL BlockBlobURL, o UploadToBlockBlobOptions) (CommonResponse, error) {
|
||||
bufferSize := int64(len(b))
|
||||
if o.BlockSize == 0 {
|
||||
// If bufferSize > (BlockBlobMaxStageBlockBytes * BlockBlobMaxBlocks), then error
|
||||
if bufferSize > BlockBlobMaxStageBlockBytes*BlockBlobMaxBlocks {
|
||||
return nil, errors.New("buffer is too large to upload to a block blob")
|
||||
}
|
||||
// If bufferSize <= BlockBlobMaxUploadBlobBytes, then Upload should be used with just 1 I/O request
|
||||
if bufferSize <= BlockBlobMaxUploadBlobBytes {
|
||||
o.BlockSize = BlockBlobMaxUploadBlobBytes // Default if unspecified
|
||||
} else {
|
||||
o.BlockSize = bufferSize / BlockBlobMaxBlocks // buffer / max blocks = block size to use all 50,000 blocks
|
||||
if o.BlockSize < BlobDefaultDownloadBlockSize { // If the block size is smaller than 4MB, round up to 4MB
|
||||
o.BlockSize = BlobDefaultDownloadBlockSize
|
||||
}
|
||||
// StageBlock will be called with blockSize blocks and a Parallelism of (BufferSize / BlockSize).
|
||||
}
|
||||
}
|
||||
|
||||
if bufferSize <= BlockBlobMaxUploadBlobBytes {
|
||||
// If the size can fit in 1 Upload call, do it this way
|
||||
var body io.ReadSeeker = bytes.NewReader(b)
|
||||
if o.Progress != nil {
|
||||
body = pipeline.NewRequestBodyProgress(body, o.Progress)
|
||||
}
|
||||
return blockBlobURL.Upload(ctx, body, o.BlobHTTPHeaders, o.Metadata, o.AccessConditions)
|
||||
}
|
||||
|
||||
var numBlocks = uint16(((bufferSize - 1) / o.BlockSize) + 1)
|
||||
|
||||
blockIDList := make([]string, numBlocks) // Base-64 encoded block IDs
|
||||
progress := int64(0)
|
||||
progressLock := &sync.Mutex{}
|
||||
|
||||
err := DoBatchTransfer(ctx, BatchTransferOptions{
|
||||
OperationName: "UploadBufferToBlockBlob",
|
||||
TransferSize: bufferSize,
|
||||
ChunkSize: o.BlockSize,
|
||||
Parallelism: o.Parallelism,
|
||||
Operation: func(offset int64, count int64, ctx context.Context) error {
|
||||
// This function is called once per block.
|
||||
// It is passed this block's offset within the buffer and its count of bytes
|
||||
// Prepare to read the proper block/section of the buffer
|
||||
var body io.ReadSeeker = bytes.NewReader(b[offset : offset+count])
|
||||
blockNum := offset / o.BlockSize
|
||||
if o.Progress != nil {
|
||||
blockProgress := int64(0)
|
||||
body = pipeline.NewRequestBodyProgress(body,
|
||||
func(bytesTransferred int64) {
|
||||
diff := bytesTransferred - blockProgress
|
||||
blockProgress = bytesTransferred
|
||||
progressLock.Lock() // 1 goroutine at a time gets a progress report
|
||||
progress += diff
|
||||
o.Progress(progress)
|
||||
progressLock.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
// Block IDs are unique values to avoid issue if 2+ clients are uploading blocks
|
||||
// at the same time causing PutBlockList to get a mix of blocks from all the clients.
|
||||
blockIDList[blockNum] = base64.StdEncoding.EncodeToString(newUUID().bytes())
|
||||
_, err := blockBlobURL.StageBlock(ctx, blockIDList[blockNum], body, o.AccessConditions.LeaseAccessConditions, nil)
|
||||
return err
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// All put blocks were successful, call Put Block List to finalize the blob
|
||||
return blockBlobURL.CommitBlockList(ctx, blockIDList, o.BlobHTTPHeaders, o.Metadata, o.AccessConditions)
|
||||
}
|
||||
|
||||
// UploadFileToBlockBlob uploads a file in blocks to a block blob.
|
||||
func UploadFileToBlockBlob(ctx context.Context, file *os.File,
|
||||
blockBlobURL BlockBlobURL, o UploadToBlockBlobOptions) (CommonResponse, error) {
|
||||
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := mmf{} // Default to an empty slice; used for 0-size file
|
||||
if stat.Size() != 0 {
|
||||
m, err = newMMF(file, false, 0, int(stat.Size()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer m.unmap()
|
||||
}
|
||||
return UploadBufferToBlockBlob(ctx, m, blockBlobURL, o)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const BlobDefaultDownloadBlockSize = int64(4 * 1024 * 1024) // 4MB
|
||||
|
||||
// DownloadFromBlobOptions identifies options used by the DownloadBlobToBuffer and DownloadBlobToFile functions.
|
||||
type DownloadFromBlobOptions struct {
|
||||
// BlockSize specifies the block size to use for each parallel download; the default size is BlobDefaultDownloadBlockSize.
|
||||
BlockSize int64
|
||||
|
||||
// Progress is a function that is invoked periodically as bytes are received.
|
||||
Progress pipeline.ProgressReceiver
|
||||
|
||||
// AccessConditions indicates the access conditions used when making HTTP GET requests against the blob.
|
||||
AccessConditions BlobAccessConditions
|
||||
|
||||
// Parallelism indicates the maximum number of blocks to download in parallel (0=default)
|
||||
Parallelism uint16
|
||||
|
||||
// RetryReaderOptionsPerBlock is used when downloading each block.
|
||||
RetryReaderOptionsPerBlock RetryReaderOptions
|
||||
}
|
||||
|
||||
// downloadBlobToBuffer downloads an Azure blob to a buffer with parallel.
|
||||
func downloadBlobToBuffer(ctx context.Context, blobURL BlobURL, offset int64, count int64,
|
||||
b []byte, o DownloadFromBlobOptions, initialDownloadResponse *DownloadResponse) error {
|
||||
if o.BlockSize == 0 {
|
||||
o.BlockSize = BlobDefaultDownloadBlockSize
|
||||
}
|
||||
|
||||
if count == CountToEnd { // If size not specified, calculate it
|
||||
if initialDownloadResponse != nil {
|
||||
count = initialDownloadResponse.ContentLength() - offset // if we have the length, use it
|
||||
} else {
|
||||
// If we don't have the length at all, get it
|
||||
dr, err := blobURL.Download(ctx, 0, CountToEnd, o.AccessConditions, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
count = dr.ContentLength() - offset
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare and do parallel download.
|
||||
progress := int64(0)
|
||||
progressLock := &sync.Mutex{}
|
||||
|
||||
err := DoBatchTransfer(ctx, BatchTransferOptions{
|
||||
OperationName: "downloadBlobToBuffer",
|
||||
TransferSize: count,
|
||||
ChunkSize: o.BlockSize,
|
||||
Parallelism: o.Parallelism,
|
||||
Operation: func(chunkStart int64, count int64, ctx context.Context) error {
|
||||
dr, err := blobURL.Download(ctx, chunkStart+offset, count, o.AccessConditions, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body := dr.Body(o.RetryReaderOptionsPerBlock)
|
||||
if o.Progress != nil {
|
||||
rangeProgress := int64(0)
|
||||
body = pipeline.NewResponseBodyProgress(
|
||||
body,
|
||||
func(bytesTransferred int64) {
|
||||
diff := bytesTransferred - rangeProgress
|
||||
rangeProgress = bytesTransferred
|
||||
progressLock.Lock()
|
||||
progress += diff
|
||||
o.Progress(progress)
|
||||
progressLock.Unlock()
|
||||
})
|
||||
}
|
||||
_, err = io.ReadFull(body, b[chunkStart:chunkStart+count])
|
||||
body.Close()
|
||||
return err
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DownloadBlobToBuffer downloads an Azure blob to a buffer with parallel.
|
||||
// Offset and count are optional, pass 0 for both to download the entire blob.
|
||||
func DownloadBlobToBuffer(ctx context.Context, blobURL BlobURL, offset int64, count int64,
|
||||
b []byte, o DownloadFromBlobOptions) error {
|
||||
return downloadBlobToBuffer(ctx, blobURL, offset, count, b, o, nil)
|
||||
}
|
||||
|
||||
// DownloadBlobToFile downloads an Azure blob to a local file.
|
||||
// The file would be truncated if the size doesn't match.
|
||||
// Offset and count are optional, pass 0 for both to download the entire blob.
|
||||
func DownloadBlobToFile(ctx context.Context, blobURL BlobURL, offset int64, count int64,
|
||||
file *os.File, o DownloadFromBlobOptions) error {
|
||||
// 1. Calculate the size of the destination file
|
||||
var size int64
|
||||
|
||||
if count == CountToEnd {
|
||||
// Try to get Azure blob's size
|
||||
props, err := blobURL.GetProperties(ctx, o.AccessConditions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
size = props.ContentLength() - offset
|
||||
} else {
|
||||
size = count
|
||||
}
|
||||
|
||||
// 2. Compare and try to resize local file's size if it doesn't match Azure blob's size.
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stat.Size() != size {
|
||||
if err = file.Truncate(size); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if size > 0 {
|
||||
// 3. Set mmap and call downloadBlobToBuffer.
|
||||
m, err := newMMF(file, true, 0, int(size))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer m.unmap()
|
||||
return downloadBlobToBuffer(ctx, blobURL, offset, size, m, o, nil)
|
||||
} else { // if the blob's size is 0, there is no need in downloading it
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// BatchTransferOptions identifies options used by DoBatchTransfer.
|
||||
type BatchTransferOptions struct {
|
||||
TransferSize int64
|
||||
ChunkSize int64
|
||||
Parallelism uint16
|
||||
Operation func(offset int64, chunkSize int64, ctx context.Context) error
|
||||
OperationName string
|
||||
}
|
||||
|
||||
// DoBatchTransfer helps to execute operations in a batch manner.
|
||||
// Can be used by users to customize batch works (for other scenarios that the SDK does not provide)
|
||||
func DoBatchTransfer(ctx context.Context, o BatchTransferOptions) error {
|
||||
if o.ChunkSize == 0 {
|
||||
return errors.New("ChunkSize cannot be 0")
|
||||
}
|
||||
|
||||
if o.Parallelism == 0 {
|
||||
o.Parallelism = 5 // default Parallelism
|
||||
}
|
||||
|
||||
// Prepare and do parallel operations.
|
||||
numChunks := uint16(((o.TransferSize - 1) / o.ChunkSize) + 1)
|
||||
operationChannel := make(chan func() error, o.Parallelism) // Create the channel that release 'Parallelism' goroutines concurrently
|
||||
operationResponseChannel := make(chan error, numChunks) // Holds each response
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
// Create the goroutines that process each operation (in parallel).
|
||||
for g := uint16(0); g < o.Parallelism; g++ {
|
||||
//grIndex := g
|
||||
go func() {
|
||||
for f := range operationChannel {
|
||||
err := f()
|
||||
operationResponseChannel <- err
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Add each chunk's operation to the channel.
|
||||
for chunkNum := uint16(0); chunkNum < numChunks; chunkNum++ {
|
||||
curChunkSize := o.ChunkSize
|
||||
|
||||
if chunkNum == numChunks-1 { // Last chunk
|
||||
curChunkSize = o.TransferSize - (int64(chunkNum) * o.ChunkSize) // Remove size of all transferred chunks from total
|
||||
}
|
||||
offset := int64(chunkNum) * o.ChunkSize
|
||||
|
||||
operationChannel <- func() error {
|
||||
return o.Operation(offset, curChunkSize, ctx)
|
||||
}
|
||||
}
|
||||
close(operationChannel)
|
||||
|
||||
// Wait for the operations to complete.
|
||||
var firstErr error = nil
|
||||
for chunkNum := uint16(0); chunkNum < numChunks; chunkNum++ {
|
||||
responseError := <-operationResponseChannel
|
||||
// record the first error (the original error which should cause the other chunks to fail with canceled context)
|
||||
if responseError != nil && firstErr == nil {
|
||||
cancel() // As soon as any operation fails, cancel all remaining operation calls
|
||||
firstErr = responseError
|
||||
}
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const _1MiB = 1024 * 1024
|
||||
|
||||
type UploadStreamToBlockBlobOptions struct {
|
||||
// BufferSize sizes the buffer used to read data from source. If < 1 MiB, defaults to 1 MiB.
|
||||
BufferSize int
|
||||
// MaxBuffers defines the number of simultaneous uploads will be performed to upload the file.
|
||||
MaxBuffers int
|
||||
BlobHTTPHeaders BlobHTTPHeaders
|
||||
Metadata Metadata
|
||||
AccessConditions BlobAccessConditions
|
||||
}
|
||||
|
||||
func (u *UploadStreamToBlockBlobOptions) defaults() {
|
||||
if u.MaxBuffers == 0 {
|
||||
u.MaxBuffers = 1
|
||||
}
|
||||
|
||||
if u.BufferSize < _1MiB {
|
||||
u.BufferSize = _1MiB
|
||||
}
|
||||
}
|
||||
|
||||
// UploadStreamToBlockBlob copies the file held in io.Reader to the Blob at blockBlobURL.
|
||||
// A Context deadline or cancellation will cause this to error.
|
||||
func UploadStreamToBlockBlob(ctx context.Context, reader io.Reader, blockBlobURL BlockBlobURL,
|
||||
o UploadStreamToBlockBlobOptions) (CommonResponse, error) {
|
||||
o.defaults()
|
||||
|
||||
result, err := copyFromReader(ctx, reader, blockBlobURL, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// UploadStreamOptions (defunct) was used internally. This will be removed or made private in a future version.
|
||||
type UploadStreamOptions struct {
|
||||
BufferSize int
|
||||
MaxBuffers int
|
||||
}
|
153
vendor/github.com/Azure/azure-storage-blob-go/azblob/parsing_urls.go
generated
vendored
Normal file
153
vendor/github.com/Azure/azure-storage-blob-go/azblob/parsing_urls.go
generated
vendored
Normal file
|
@ -0,0 +1,153 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
snapshot = "snapshot"
|
||||
SnapshotTimeFormat = "2006-01-02T15:04:05.0000000Z07:00"
|
||||
)
|
||||
|
||||
// A BlobURLParts object represents the components that make up an Azure Storage Container/Blob URL. You parse an
|
||||
// existing URL into its parts by calling NewBlobURLParts(). You construct a URL from parts by calling URL().
|
||||
// NOTE: Changing any SAS-related field requires computing a new SAS signature.
|
||||
type BlobURLParts struct {
|
||||
Scheme string // Ex: "https://"
|
||||
Host string // Ex: "account.blob.core.windows.net", "10.132.141.33", "10.132.141.33:80"
|
||||
IPEndpointStyleInfo IPEndpointStyleInfo
|
||||
ContainerName string // "" if no container
|
||||
BlobName string // "" if no blob
|
||||
Snapshot string // "" if not a snapshot
|
||||
SAS SASQueryParameters
|
||||
UnparsedParams string
|
||||
}
|
||||
|
||||
// IPEndpointStyleInfo is used for IP endpoint style URL when working with Azure storage emulator.
|
||||
// Ex: "https://10.132.141.33/accountname/containername"
|
||||
type IPEndpointStyleInfo struct {
|
||||
AccountName string // "" if not using IP endpoint style
|
||||
}
|
||||
|
||||
// isIPEndpointStyle checkes if URL's host is IP, in this case the storage account endpoint will be composed as:
|
||||
// http(s)://IP(:port)/storageaccount/container/...
|
||||
// As url's Host property, host could be both host or host:port
|
||||
func isIPEndpointStyle(host string) bool {
|
||||
if host == "" {
|
||||
return false
|
||||
}
|
||||
if h, _, err := net.SplitHostPort(host); err == nil {
|
||||
host = h
|
||||
}
|
||||
// For IPv6, there could be case where SplitHostPort fails for cannot finding port.
|
||||
// In this case, eliminate the '[' and ']' in the URL.
|
||||
// For details about IPv6 URL, please refer to https://tools.ietf.org/html/rfc2732
|
||||
if host[0] == '[' && host[len(host)-1] == ']' {
|
||||
host = host[1 : len(host)-1]
|
||||
}
|
||||
return net.ParseIP(host) != nil
|
||||
}
|
||||
|
||||
// NewBlobURLParts parses a URL initializing BlobURLParts' fields including any SAS-related & snapshot query parameters. Any other
|
||||
// query parameters remain in the UnparsedParams field. This method overwrites all fields in the BlobURLParts object.
|
||||
func NewBlobURLParts(u url.URL) BlobURLParts {
|
||||
up := BlobURLParts{
|
||||
Scheme: u.Scheme,
|
||||
Host: u.Host,
|
||||
}
|
||||
|
||||
// Find the container & blob names (if any)
|
||||
if u.Path != "" {
|
||||
path := u.Path
|
||||
if path[0] == '/' {
|
||||
path = path[1:] // If path starts with a slash, remove it
|
||||
}
|
||||
if isIPEndpointStyle(up.Host) {
|
||||
if accountEndIndex := strings.Index(path, "/"); accountEndIndex == -1 { // Slash not found; path has account name & no container name or blob
|
||||
up.IPEndpointStyleInfo.AccountName = path
|
||||
} else {
|
||||
up.IPEndpointStyleInfo.AccountName = path[:accountEndIndex] // The account name is the part between the slashes
|
||||
path = path[accountEndIndex+1:] // path refers to portion after the account name now (container & blob names)
|
||||
}
|
||||
}
|
||||
|
||||
containerEndIndex := strings.Index(path, "/") // Find the next slash (if it exists)
|
||||
if containerEndIndex == -1 { // Slash not found; path has container name & no blob name
|
||||
up.ContainerName = path
|
||||
} else {
|
||||
up.ContainerName = path[:containerEndIndex] // The container name is the part between the slashes
|
||||
up.BlobName = path[containerEndIndex+1:] // The blob name is after the container slash
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the query parameters to a case-sensitive map & trim whitespace
|
||||
paramsMap := u.Query()
|
||||
|
||||
up.Snapshot = "" // Assume no snapshot
|
||||
if snapshotStr, ok := caseInsensitiveValues(paramsMap).Get(snapshot); ok {
|
||||
up.Snapshot = snapshotStr[0]
|
||||
// If we recognized the query parameter, remove it from the map
|
||||
delete(paramsMap, snapshot)
|
||||
}
|
||||
up.SAS = newSASQueryParameters(paramsMap, true)
|
||||
up.UnparsedParams = paramsMap.Encode()
|
||||
return up
|
||||
}
|
||||
|
||||
type caseInsensitiveValues url.Values // map[string][]string
|
||||
func (values caseInsensitiveValues) Get(key string) ([]string, bool) {
|
||||
key = strings.ToLower(key)
|
||||
for k, v := range values {
|
||||
if strings.ToLower(k) == key {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
return []string{}, false
|
||||
}
|
||||
|
||||
// URL returns a URL object whose fields are initialized from the BlobURLParts fields. The URL's RawQuery
|
||||
// field contains the SAS, snapshot, and unparsed query parameters.
|
||||
func (up BlobURLParts) URL() url.URL {
|
||||
path := ""
|
||||
if isIPEndpointStyle(up.Host) && up.IPEndpointStyleInfo.AccountName != "" {
|
||||
path += "/" + up.IPEndpointStyleInfo.AccountName
|
||||
}
|
||||
// Concatenate container & blob names (if they exist)
|
||||
if up.ContainerName != "" {
|
||||
path += "/" + up.ContainerName
|
||||
if up.BlobName != "" {
|
||||
path += "/" + up.BlobName
|
||||
}
|
||||
}
|
||||
|
||||
rawQuery := up.UnparsedParams
|
||||
|
||||
//If no snapshot is initially provided, fill it in from the SAS query properties to help the user
|
||||
if up.Snapshot == "" && !up.SAS.snapshotTime.IsZero() {
|
||||
up.Snapshot = up.SAS.snapshotTime.Format(SnapshotTimeFormat)
|
||||
}
|
||||
|
||||
// Concatenate blob snapshot query parameter (if it exists)
|
||||
if up.Snapshot != "" {
|
||||
if len(rawQuery) > 0 {
|
||||
rawQuery += "&"
|
||||
}
|
||||
rawQuery += snapshot + "=" + up.Snapshot
|
||||
}
|
||||
sas := up.SAS.Encode()
|
||||
if sas != "" {
|
||||
if len(rawQuery) > 0 {
|
||||
rawQuery += "&"
|
||||
}
|
||||
rawQuery += sas
|
||||
}
|
||||
u := url.URL{
|
||||
Scheme: up.Scheme,
|
||||
Host: up.Host,
|
||||
Path: path,
|
||||
RawQuery: rawQuery,
|
||||
}
|
||||
return u
|
||||
}
|
256
vendor/github.com/Azure/azure-storage-blob-go/azblob/sas_service.go
generated
vendored
Normal file
256
vendor/github.com/Azure/azure-storage-blob-go/azblob/sas_service.go
generated
vendored
Normal file
|
@ -0,0 +1,256 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BlobSASSignatureValues is used to generate a Shared Access Signature (SAS) for an Azure Storage container or blob.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/constructing-a-service-sas
|
||||
type BlobSASSignatureValues struct {
|
||||
Version string `param:"sv"` // If not specified, this defaults to SASVersion
|
||||
Protocol SASProtocol `param:"spr"` // See the SASProtocol* constants
|
||||
StartTime time.Time `param:"st"` // Not specified if IsZero
|
||||
ExpiryTime time.Time `param:"se"` // Not specified if IsZero
|
||||
SnapshotTime time.Time
|
||||
Permissions string `param:"sp"` // Create by initializing a ContainerSASPermissions or BlobSASPermissions and then call String()
|
||||
IPRange IPRange `param:"sip"`
|
||||
Identifier string `param:"si"`
|
||||
ContainerName string
|
||||
BlobName string // Use "" to create a Container SAS
|
||||
CacheControl string // rscc
|
||||
ContentDisposition string // rscd
|
||||
ContentEncoding string // rsce
|
||||
ContentLanguage string // rscl
|
||||
ContentType string // rsct
|
||||
}
|
||||
|
||||
// NewSASQueryParameters uses an account's StorageAccountCredential to sign this signature values to produce
|
||||
// the proper SAS query parameters.
|
||||
// See: StorageAccountCredential. Compatible with both UserDelegationCredential and SharedKeyCredential
|
||||
func (v BlobSASSignatureValues) NewSASQueryParameters(credential StorageAccountCredential) (SASQueryParameters, error) {
|
||||
resource := "c"
|
||||
if credential == nil {
|
||||
return SASQueryParameters{}, fmt.Errorf("cannot sign SAS query without StorageAccountCredential")
|
||||
}
|
||||
|
||||
if !v.SnapshotTime.IsZero() {
|
||||
resource = "bs"
|
||||
//Make sure the permission characters are in the correct order
|
||||
perms := &BlobSASPermissions{}
|
||||
if err := perms.Parse(v.Permissions); err != nil {
|
||||
return SASQueryParameters{}, err
|
||||
}
|
||||
v.Permissions = perms.String()
|
||||
} else if v.BlobName == "" {
|
||||
// Make sure the permission characters are in the correct order
|
||||
perms := &ContainerSASPermissions{}
|
||||
if err := perms.Parse(v.Permissions); err != nil {
|
||||
return SASQueryParameters{}, err
|
||||
}
|
||||
v.Permissions = perms.String()
|
||||
} else {
|
||||
resource = "b"
|
||||
// Make sure the permission characters are in the correct order
|
||||
perms := &BlobSASPermissions{}
|
||||
if err := perms.Parse(v.Permissions); err != nil {
|
||||
return SASQueryParameters{}, err
|
||||
}
|
||||
v.Permissions = perms.String()
|
||||
}
|
||||
if v.Version == "" {
|
||||
v.Version = SASVersion
|
||||
}
|
||||
startTime, expiryTime, snapshotTime := FormatTimesForSASSigning(v.StartTime, v.ExpiryTime, v.SnapshotTime)
|
||||
|
||||
signedIdentifier := v.Identifier
|
||||
|
||||
udk := credential.getUDKParams()
|
||||
|
||||
if udk != nil {
|
||||
udkStart, udkExpiry, _ := FormatTimesForSASSigning(udk.SignedStart, udk.SignedExpiry, time.Time{})
|
||||
//I don't like this answer to combining the functions
|
||||
//But because signedIdentifier and the user delegation key strings share a place, this is an _OK_ way to do it.
|
||||
signedIdentifier = strings.Join([]string{
|
||||
udk.SignedOid,
|
||||
udk.SignedTid,
|
||||
udkStart,
|
||||
udkExpiry,
|
||||
udk.SignedService,
|
||||
udk.SignedVersion,
|
||||
}, "\n")
|
||||
}
|
||||
|
||||
// String to sign: http://msdn.microsoft.com/en-us/library/azure/dn140255.aspx
|
||||
stringToSign := strings.Join([]string{
|
||||
v.Permissions,
|
||||
startTime,
|
||||
expiryTime,
|
||||
getCanonicalName(credential.AccountName(), v.ContainerName, v.BlobName),
|
||||
signedIdentifier,
|
||||
v.IPRange.String(),
|
||||
string(v.Protocol),
|
||||
v.Version,
|
||||
resource,
|
||||
snapshotTime, // signed timestamp
|
||||
v.CacheControl, // rscc
|
||||
v.ContentDisposition, // rscd
|
||||
v.ContentEncoding, // rsce
|
||||
v.ContentLanguage, // rscl
|
||||
v.ContentType}, // rsct
|
||||
"\n")
|
||||
|
||||
signature := ""
|
||||
signature = credential.ComputeHMACSHA256(stringToSign)
|
||||
|
||||
p := SASQueryParameters{
|
||||
// Common SAS parameters
|
||||
version: v.Version,
|
||||
protocol: v.Protocol,
|
||||
startTime: v.StartTime,
|
||||
expiryTime: v.ExpiryTime,
|
||||
permissions: v.Permissions,
|
||||
ipRange: v.IPRange,
|
||||
|
||||
// Container/Blob-specific SAS parameters
|
||||
resource: resource,
|
||||
identifier: v.Identifier,
|
||||
cacheControl: v.CacheControl,
|
||||
contentDisposition: v.ContentDisposition,
|
||||
contentEncoding: v.ContentEncoding,
|
||||
contentLanguage: v.ContentLanguage,
|
||||
contentType: v.ContentType,
|
||||
snapshotTime: v.SnapshotTime,
|
||||
|
||||
// Calculated SAS signature
|
||||
signature: signature,
|
||||
}
|
||||
|
||||
//User delegation SAS specific parameters
|
||||
if udk != nil {
|
||||
p.signedOid = udk.SignedOid
|
||||
p.signedTid = udk.SignedTid
|
||||
p.signedStart = udk.SignedStart
|
||||
p.signedExpiry = udk.SignedExpiry
|
||||
p.signedService = udk.SignedService
|
||||
p.signedVersion = udk.SignedVersion
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// getCanonicalName computes the canonical name for a container or blob resource for SAS signing.
|
||||
func getCanonicalName(account string, containerName string, blobName string) string {
|
||||
// Container: "/blob/account/containername"
|
||||
// Blob: "/blob/account/containername/blobname"
|
||||
elements := []string{"/blob/", account, "/", containerName}
|
||||
if blobName != "" {
|
||||
elements = append(elements, "/", strings.Replace(blobName, "\\", "/", -1))
|
||||
}
|
||||
return strings.Join(elements, "")
|
||||
}
|
||||
|
||||
// The ContainerSASPermissions type simplifies creating the permissions string for an Azure Storage container SAS.
|
||||
// Initialize an instance of this type and then call its String method to set BlobSASSignatureValues's Permissions field.
|
||||
type ContainerSASPermissions struct {
|
||||
Read, Add, Create, Write, Delete, List bool
|
||||
}
|
||||
|
||||
// String produces the SAS permissions string for an Azure Storage container.
|
||||
// Call this method to set BlobSASSignatureValues's Permissions field.
|
||||
func (p ContainerSASPermissions) String() string {
|
||||
var b bytes.Buffer
|
||||
if p.Read {
|
||||
b.WriteRune('r')
|
||||
}
|
||||
if p.Add {
|
||||
b.WriteRune('a')
|
||||
}
|
||||
if p.Create {
|
||||
b.WriteRune('c')
|
||||
}
|
||||
if p.Write {
|
||||
b.WriteRune('w')
|
||||
}
|
||||
if p.Delete {
|
||||
b.WriteRune('d')
|
||||
}
|
||||
if p.List {
|
||||
b.WriteRune('l')
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Parse initializes the ContainerSASPermissions's fields from a string.
|
||||
func (p *ContainerSASPermissions) Parse(s string) error {
|
||||
*p = ContainerSASPermissions{} // Clear the flags
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case 'r':
|
||||
p.Read = true
|
||||
case 'a':
|
||||
p.Add = true
|
||||
case 'c':
|
||||
p.Create = true
|
||||
case 'w':
|
||||
p.Write = true
|
||||
case 'd':
|
||||
p.Delete = true
|
||||
case 'l':
|
||||
p.List = true
|
||||
default:
|
||||
return fmt.Errorf("Invalid permission: '%v'", r)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// The BlobSASPermissions type simplifies creating the permissions string for an Azure Storage blob SAS.
|
||||
// Initialize an instance of this type and then call its String method to set BlobSASSignatureValues's Permissions field.
|
||||
type BlobSASPermissions struct{ Read, Add, Create, Write, Delete bool }
|
||||
|
||||
// String produces the SAS permissions string for an Azure Storage blob.
|
||||
// Call this method to set BlobSASSignatureValues's Permissions field.
|
||||
func (p BlobSASPermissions) String() string {
|
||||
var b bytes.Buffer
|
||||
if p.Read {
|
||||
b.WriteRune('r')
|
||||
}
|
||||
if p.Add {
|
||||
b.WriteRune('a')
|
||||
}
|
||||
if p.Create {
|
||||
b.WriteRune('c')
|
||||
}
|
||||
if p.Write {
|
||||
b.WriteRune('w')
|
||||
}
|
||||
if p.Delete {
|
||||
b.WriteRune('d')
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Parse initializes the BlobSASPermissions's fields from a string.
|
||||
func (p *BlobSASPermissions) Parse(s string) error {
|
||||
*p = BlobSASPermissions{} // Clear the flags
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case 'r':
|
||||
p.Read = true
|
||||
case 'a':
|
||||
p.Add = true
|
||||
case 'c':
|
||||
p.Create = true
|
||||
case 'w':
|
||||
p.Write = true
|
||||
case 'd':
|
||||
p.Delete = true
|
||||
default:
|
||||
return fmt.Errorf("Invalid permission: '%v'", r)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
195
vendor/github.com/Azure/azure-storage-blob-go/azblob/service_codes_blob.go
generated
vendored
Normal file
195
vendor/github.com/Azure/azure-storage-blob-go/azblob/service_codes_blob.go
generated
vendored
Normal file
|
@ -0,0 +1,195 @@
|
|||
package azblob
|
||||
|
||||
// https://docs.microsoft.com/en-us/rest/api/storageservices/blob-service-error-codes
|
||||
|
||||
// ServiceCode values indicate a service failure.
|
||||
const (
|
||||
// ServiceCodeAppendPositionConditionNotMet means the append position condition specified was not met.
|
||||
ServiceCodeAppendPositionConditionNotMet ServiceCodeType = "AppendPositionConditionNotMet"
|
||||
|
||||
// ServiceCodeBlobAlreadyExists means the specified blob already exists.
|
||||
ServiceCodeBlobAlreadyExists ServiceCodeType = "BlobAlreadyExists"
|
||||
|
||||
// ServiceCodeBlobNotFound means the specified blob does not exist.
|
||||
ServiceCodeBlobNotFound ServiceCodeType = "BlobNotFound"
|
||||
|
||||
// ServiceCodeBlobOverwritten means the blob has been recreated since the previous snapshot was taken.
|
||||
ServiceCodeBlobOverwritten ServiceCodeType = "BlobOverwritten"
|
||||
|
||||
// ServiceCodeBlobTierInadequateForContentLength means the specified blob tier size limit cannot be less than content length.
|
||||
ServiceCodeBlobTierInadequateForContentLength ServiceCodeType = "BlobTierInadequateForContentLength"
|
||||
|
||||
// ServiceCodeBlockCountExceedsLimit means the committed block count cannot exceed the maximum limit of 50,000 blocks
|
||||
// or that the uncommitted block count cannot exceed the maximum limit of 100,000 blocks.
|
||||
ServiceCodeBlockCountExceedsLimit ServiceCodeType = "BlockCountExceedsLimit"
|
||||
|
||||
// ServiceCodeBlockListTooLong means the block list may not contain more than 50,000 blocks.
|
||||
ServiceCodeBlockListTooLong ServiceCodeType = "BlockListTooLong"
|
||||
|
||||
// ServiceCodeCannotChangeToLowerTier means that a higher blob tier has already been explicitly set.
|
||||
ServiceCodeCannotChangeToLowerTier ServiceCodeType = "CannotChangeToLowerTier"
|
||||
|
||||
// ServiceCodeCannotVerifyCopySource means that the service could not verify the copy source within the specified time.
|
||||
// Examine the HTTP status code and message for more information about the failure.
|
||||
ServiceCodeCannotVerifyCopySource ServiceCodeType = "CannotVerifyCopySource"
|
||||
|
||||
// ServiceCodeContainerAlreadyExists means the specified container already exists.
|
||||
ServiceCodeContainerAlreadyExists ServiceCodeType = "ContainerAlreadyExists"
|
||||
|
||||
// ServiceCodeContainerBeingDeleted means the specified container is being deleted.
|
||||
ServiceCodeContainerBeingDeleted ServiceCodeType = "ContainerBeingDeleted"
|
||||
|
||||
// ServiceCodeContainerDisabled means the specified container has been disabled by the administrator.
|
||||
ServiceCodeContainerDisabled ServiceCodeType = "ContainerDisabled"
|
||||
|
||||
// ServiceCodeContainerNotFound means the specified container does not exist.
|
||||
ServiceCodeContainerNotFound ServiceCodeType = "ContainerNotFound"
|
||||
|
||||
// ServiceCodeContentLengthLargerThanTierLimit means the blob's content length cannot exceed its tier limit.
|
||||
ServiceCodeContentLengthLargerThanTierLimit ServiceCodeType = "ContentLengthLargerThanTierLimit"
|
||||
|
||||
// ServiceCodeCopyAcrossAccountsNotSupported means the copy source account and destination account must be the same.
|
||||
ServiceCodeCopyAcrossAccountsNotSupported ServiceCodeType = "CopyAcrossAccountsNotSupported"
|
||||
|
||||
// ServiceCodeCopyIDMismatch means the specified copy ID did not match the copy ID for the pending copy operation.
|
||||
ServiceCodeCopyIDMismatch ServiceCodeType = "CopyIdMismatch"
|
||||
|
||||
// ServiceCodeFeatureVersionMismatch means the type of blob in the container is unrecognized by this version or
|
||||
// that the operation for AppendBlob requires at least version 2015-02-21.
|
||||
ServiceCodeFeatureVersionMismatch ServiceCodeType = "FeatureVersionMismatch"
|
||||
|
||||
// ServiceCodeIncrementalCopyBlobMismatch means the specified source blob is different than the copy source of the existing incremental copy blob.
|
||||
ServiceCodeIncrementalCopyBlobMismatch ServiceCodeType = "IncrementalCopyBlobMismatch"
|
||||
|
||||
// ServiceCodeIncrementalCopyOfEralierVersionSnapshotNotAllowed means the specified snapshot is earlier than the last snapshot copied into the incremental copy blob.
|
||||
ServiceCodeIncrementalCopyOfEralierVersionSnapshotNotAllowed ServiceCodeType = "IncrementalCopyOfEralierVersionSnapshotNotAllowed"
|
||||
|
||||
// ServiceCodeIncrementalCopySourceMustBeSnapshot means the source for incremental copy request must be a snapshot.
|
||||
ServiceCodeIncrementalCopySourceMustBeSnapshot ServiceCodeType = "IncrementalCopySourceMustBeSnapshot"
|
||||
|
||||
// ServiceCodeInfiniteLeaseDurationRequired means the lease ID matched, but the specified lease must be an infinite-duration lease.
|
||||
ServiceCodeInfiniteLeaseDurationRequired ServiceCodeType = "InfiniteLeaseDurationRequired"
|
||||
|
||||
// ServiceCodeInvalidBlobOrBlock means the specified blob or block content is invalid.
|
||||
ServiceCodeInvalidBlobOrBlock ServiceCodeType = "InvalidBlobOrBlock"
|
||||
|
||||
// ServiceCodeInvalidBlobType means the blob type is invalid for this operation.
|
||||
ServiceCodeInvalidBlobType ServiceCodeType = "InvalidBlobType"
|
||||
|
||||
// ServiceCodeInvalidBlockID means the specified block ID is invalid. The block ID must be Base64-encoded.
|
||||
ServiceCodeInvalidBlockID ServiceCodeType = "InvalidBlockId"
|
||||
|
||||
// ServiceCodeInvalidBlockList means the specified block list is invalid.
|
||||
ServiceCodeInvalidBlockList ServiceCodeType = "InvalidBlockList"
|
||||
|
||||
// ServiceCodeInvalidOperation means an invalid operation against a blob snapshot.
|
||||
ServiceCodeInvalidOperation ServiceCodeType = "InvalidOperation"
|
||||
|
||||
// ServiceCodeInvalidPageRange means the page range specified is invalid.
|
||||
ServiceCodeInvalidPageRange ServiceCodeType = "InvalidPageRange"
|
||||
|
||||
// ServiceCodeInvalidSourceBlobType means the copy source blob type is invalid for this operation.
|
||||
ServiceCodeInvalidSourceBlobType ServiceCodeType = "InvalidSourceBlobType"
|
||||
|
||||
// ServiceCodeInvalidSourceBlobURL means the source URL for incremental copy request must be valid Azure Storage blob URL.
|
||||
ServiceCodeInvalidSourceBlobURL ServiceCodeType = "InvalidSourceBlobUrl"
|
||||
|
||||
// ServiceCodeInvalidVersionForPageBlobOperation means that all operations on page blobs require at least version 2009-09-19.
|
||||
ServiceCodeInvalidVersionForPageBlobOperation ServiceCodeType = "InvalidVersionForPageBlobOperation"
|
||||
|
||||
// ServiceCodeLeaseAlreadyPresent means there is already a lease present.
|
||||
ServiceCodeLeaseAlreadyPresent ServiceCodeType = "LeaseAlreadyPresent"
|
||||
|
||||
// ServiceCodeLeaseAlreadyBroken means the lease has already been broken and cannot be broken again.
|
||||
ServiceCodeLeaseAlreadyBroken ServiceCodeType = "LeaseAlreadyBroken"
|
||||
|
||||
// ServiceCodeLeaseIDMismatchWithBlobOperation means the lease ID specified did not match the lease ID for the blob.
|
||||
ServiceCodeLeaseIDMismatchWithBlobOperation ServiceCodeType = "LeaseIdMismatchWithBlobOperation"
|
||||
|
||||
// ServiceCodeLeaseIDMismatchWithContainerOperation means the lease ID specified did not match the lease ID for the container.
|
||||
ServiceCodeLeaseIDMismatchWithContainerOperation ServiceCodeType = "LeaseIdMismatchWithContainerOperation"
|
||||
|
||||
// ServiceCodeLeaseIDMismatchWithLeaseOperation means the lease ID specified did not match the lease ID for the blob/container.
|
||||
ServiceCodeLeaseIDMismatchWithLeaseOperation ServiceCodeType = "LeaseIdMismatchWithLeaseOperation"
|
||||
|
||||
// ServiceCodeLeaseIDMissing means there is currently a lease on the blob/container and no lease ID was specified in the request.
|
||||
ServiceCodeLeaseIDMissing ServiceCodeType = "LeaseIdMissing"
|
||||
|
||||
// ServiceCodeLeaseIsBreakingAndCannotBeAcquired means the lease ID matched, but the lease is currently in breaking state and cannot be acquired until it is broken.
|
||||
ServiceCodeLeaseIsBreakingAndCannotBeAcquired ServiceCodeType = "LeaseIsBreakingAndCannotBeAcquired"
|
||||
|
||||
// ServiceCodeLeaseIsBreakingAndCannotBeChanged means the lease ID matched, but the lease is currently in breaking state and cannot be changed.
|
||||
ServiceCodeLeaseIsBreakingAndCannotBeChanged ServiceCodeType = "LeaseIsBreakingAndCannotBeChanged"
|
||||
|
||||
// ServiceCodeLeaseIsBrokenAndCannotBeRenewed means the lease ID matched, but the lease has been broken explicitly and cannot be renewed.
|
||||
ServiceCodeLeaseIsBrokenAndCannotBeRenewed ServiceCodeType = "LeaseIsBrokenAndCannotBeRenewed"
|
||||
|
||||
// ServiceCodeLeaseLost means a lease ID was specified, but the lease for the blob/container has expired.
|
||||
ServiceCodeLeaseLost ServiceCodeType = "LeaseLost"
|
||||
|
||||
// ServiceCodeLeaseNotPresentWithBlobOperation means there is currently no lease on the blob.
|
||||
ServiceCodeLeaseNotPresentWithBlobOperation ServiceCodeType = "LeaseNotPresentWithBlobOperation"
|
||||
|
||||
// ServiceCodeLeaseNotPresentWithContainerOperation means there is currently no lease on the container.
|
||||
ServiceCodeLeaseNotPresentWithContainerOperation ServiceCodeType = "LeaseNotPresentWithContainerOperation"
|
||||
|
||||
// ServiceCodeLeaseNotPresentWithLeaseOperation means there is currently no lease on the blob/container.
|
||||
ServiceCodeLeaseNotPresentWithLeaseOperation ServiceCodeType = "LeaseNotPresentWithLeaseOperation"
|
||||
|
||||
// ServiceCodeMaxBlobSizeConditionNotMet means the max blob size condition specified was not met.
|
||||
ServiceCodeMaxBlobSizeConditionNotMet ServiceCodeType = "MaxBlobSizeConditionNotMet"
|
||||
|
||||
// ServiceCodeNoPendingCopyOperation means there is currently no pending copy operation.
|
||||
ServiceCodeNoPendingCopyOperation ServiceCodeType = "NoPendingCopyOperation"
|
||||
|
||||
// ServiceCodeOperationNotAllowedOnIncrementalCopyBlob means the specified operation is not allowed on an incremental copy blob.
|
||||
ServiceCodeOperationNotAllowedOnIncrementalCopyBlob ServiceCodeType = "OperationNotAllowedOnIncrementalCopyBlob"
|
||||
|
||||
// ServiceCodePendingCopyOperation means there is currently a pending copy operation.
|
||||
ServiceCodePendingCopyOperation ServiceCodeType = "PendingCopyOperation"
|
||||
|
||||
// ServiceCodePreviousSnapshotCannotBeNewer means the prevsnapshot query parameter value cannot be newer than snapshot query parameter value.
|
||||
ServiceCodePreviousSnapshotCannotBeNewer ServiceCodeType = "PreviousSnapshotCannotBeNewer"
|
||||
|
||||
// ServiceCodePreviousSnapshotNotFound means the previous snapshot is not found.
|
||||
ServiceCodePreviousSnapshotNotFound ServiceCodeType = "PreviousSnapshotNotFound"
|
||||
|
||||
// ServiceCodePreviousSnapshotOperationNotSupported means that differential Get Page Ranges is not supported on the previous snapshot.
|
||||
ServiceCodePreviousSnapshotOperationNotSupported ServiceCodeType = "PreviousSnapshotOperationNotSupported"
|
||||
|
||||
// ServiceCodeSequenceNumberConditionNotMet means the sequence number condition specified was not met.
|
||||
ServiceCodeSequenceNumberConditionNotMet ServiceCodeType = "SequenceNumberConditionNotMet"
|
||||
|
||||
// ServiceCodeSequenceNumberIncrementTooLarge means the sequence number increment cannot be performed because it would result in overflow of the sequence number.
|
||||
ServiceCodeSequenceNumberIncrementTooLarge ServiceCodeType = "SequenceNumberIncrementTooLarge"
|
||||
|
||||
// ServiceCodeSnapshotCountExceeded means the snapshot count against this blob has been exceeded.
|
||||
ServiceCodeSnapshotCountExceeded ServiceCodeType = "SnapshotCountExceeded"
|
||||
|
||||
// ServiceCodeSnaphotOperationRateExceeded means the rate of snapshot operations against this blob has been exceeded.
|
||||
ServiceCodeSnaphotOperationRateExceeded ServiceCodeType = "SnaphotOperationRateExceeded"
|
||||
|
||||
// ServiceCodeSnapshotsPresent means this operation is not permitted while the blob has snapshots.
|
||||
ServiceCodeSnapshotsPresent ServiceCodeType = "SnapshotsPresent"
|
||||
|
||||
// ServiceCodeSourceConditionNotMet means the source condition specified using HTTP conditional header(s) is not met.
|
||||
ServiceCodeSourceConditionNotMet ServiceCodeType = "SourceConditionNotMet"
|
||||
|
||||
// ServiceCodeSystemInUse means this blob is in use by the system.
|
||||
ServiceCodeSystemInUse ServiceCodeType = "SystemInUse"
|
||||
|
||||
// ServiceCodeTargetConditionNotMet means the target condition specified using HTTP conditional header(s) is not met.
|
||||
ServiceCodeTargetConditionNotMet ServiceCodeType = "TargetConditionNotMet"
|
||||
|
||||
// ServiceCodeUnauthorizedBlobOverwrite means this request is not authorized to perform blob overwrites.
|
||||
ServiceCodeUnauthorizedBlobOverwrite ServiceCodeType = "UnauthorizedBlobOverwrite"
|
||||
|
||||
// ServiceCodeBlobBeingRehydrated means this operation is not permitted because the blob is being rehydrated.
|
||||
ServiceCodeBlobBeingRehydrated ServiceCodeType = "BlobBeingRehydrated"
|
||||
|
||||
// ServiceCodeBlobArchived means this operation is not permitted on an archived blob.
|
||||
ServiceCodeBlobArchived ServiceCodeType = "BlobArchived"
|
||||
|
||||
// ServiceCodeBlobNotArchived means this blob is currently not in the archived state.
|
||||
ServiceCodeBlobNotArchived ServiceCodeType = "BlobNotArchived"
|
||||
)
|
8
vendor/github.com/Azure/azure-storage-blob-go/azblob/storage_account_credential.go
generated
vendored
Normal file
8
vendor/github.com/Azure/azure-storage-blob-go/azblob/storage_account_credential.go
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
package azblob
|
||||
|
||||
// StorageAccountCredential is a wrapper interface for SharedKeyCredential and UserDelegationCredential
|
||||
type StorageAccountCredential interface {
|
||||
AccountName() string
|
||||
ComputeHMACSHA256(message string) (base64String string)
|
||||
getUDKParams() *UserDelegationKey
|
||||
}
|
138
vendor/github.com/Azure/azure-storage-blob-go/azblob/url_append_blob.go
generated
vendored
Normal file
138
vendor/github.com/Azure/azure-storage-blob-go/azblob/url_append_blob.go
generated
vendored
Normal file
|
@ -0,0 +1,138 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
)
|
||||
|
||||
const (
|
||||
// AppendBlobMaxAppendBlockBytes indicates the maximum number of bytes that can be sent in a call to AppendBlock.
|
||||
AppendBlobMaxAppendBlockBytes = 4 * 1024 * 1024 // 4MB
|
||||
|
||||
// AppendBlobMaxBlocks indicates the maximum number of blocks allowed in an append blob.
|
||||
AppendBlobMaxBlocks = 50000
|
||||
)
|
||||
|
||||
// AppendBlobURL defines a set of operations applicable to append blobs.
|
||||
type AppendBlobURL struct {
|
||||
BlobURL
|
||||
abClient appendBlobClient
|
||||
}
|
||||
|
||||
// NewAppendBlobURL creates an AppendBlobURL object using the specified URL and request policy pipeline.
|
||||
func NewAppendBlobURL(url url.URL, p pipeline.Pipeline) AppendBlobURL {
|
||||
blobClient := newBlobClient(url, p)
|
||||
abClient := newAppendBlobClient(url, p)
|
||||
return AppendBlobURL{BlobURL: BlobURL{blobClient: blobClient}, abClient: abClient}
|
||||
}
|
||||
|
||||
// WithPipeline creates a new AppendBlobURL object identical to the source but with the specific request policy pipeline.
|
||||
func (ab AppendBlobURL) WithPipeline(p pipeline.Pipeline) AppendBlobURL {
|
||||
return NewAppendBlobURL(ab.blobClient.URL(), p)
|
||||
}
|
||||
|
||||
// WithSnapshot creates a new AppendBlobURL object identical to the source but with the specified snapshot timestamp.
|
||||
// Pass "" to remove the snapshot returning a URL to the base blob.
|
||||
func (ab AppendBlobURL) WithSnapshot(snapshot string) AppendBlobURL {
|
||||
p := NewBlobURLParts(ab.URL())
|
||||
p.Snapshot = snapshot
|
||||
return NewAppendBlobURL(p.URL(), ab.blobClient.Pipeline())
|
||||
}
|
||||
|
||||
func (ab AppendBlobURL) GetAccountInfo(ctx context.Context) (*BlobGetAccountInfoResponse, error) {
|
||||
return ab.blobClient.GetAccountInfo(ctx)
|
||||
}
|
||||
|
||||
// Create creates a 0-length append blob. Call AppendBlock to append data to an append blob.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/put-blob.
|
||||
func (ab AppendBlobURL) Create(ctx context.Context, h BlobHTTPHeaders, metadata Metadata, ac BlobAccessConditions) (*AppendBlobCreateResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch := ac.ModifiedAccessConditions.pointers()
|
||||
return ab.abClient.Create(ctx, 0, nil,
|
||||
&h.ContentType, &h.ContentEncoding, &h.ContentLanguage, h.ContentMD5,
|
||||
&h.CacheControl, metadata, ac.LeaseAccessConditions.pointers(), &h.ContentDisposition,
|
||||
nil, nil, EncryptionAlgorithmNone, // CPK
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, nil)
|
||||
}
|
||||
|
||||
// AppendBlock writes a stream to a new block of data to the end of the existing append blob.
|
||||
// This method panics if the stream is not at position 0.
|
||||
// Note that the http client closes the body stream after the request is sent to the service.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/append-block.
|
||||
func (ab AppendBlobURL) AppendBlock(ctx context.Context, body io.ReadSeeker, ac AppendBlobAccessConditions, transactionalMD5 []byte) (*AppendBlobAppendBlockResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
|
||||
ifAppendPositionEqual, ifMaxSizeLessThanOrEqual := ac.AppendPositionAccessConditions.pointers()
|
||||
count, err := validateSeekableStreamAt0AndGetCount(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ab.abClient.AppendBlock(ctx, body, count, nil,
|
||||
transactionalMD5,
|
||||
nil, // CRC
|
||||
ac.LeaseAccessConditions.pointers(),
|
||||
ifMaxSizeLessThanOrEqual, ifAppendPositionEqual,
|
||||
nil, nil, EncryptionAlgorithmNone, // CPK
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
|
||||
}
|
||||
|
||||
// AppendBlockFromURL copies a new block of data from source URL to the end of the existing append blob.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/append-block-from-url.
|
||||
func (ab AppendBlobURL) AppendBlockFromURL(ctx context.Context, sourceURL url.URL, offset int64, count int64, destinationAccessConditions AppendBlobAccessConditions, sourceAccessConditions ModifiedAccessConditions, transactionalMD5 []byte) (*AppendBlobAppendBlockFromURLResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := destinationAccessConditions.ModifiedAccessConditions.pointers()
|
||||
sourceIfModifiedSince, sourceIfUnmodifiedSince, sourceIfMatchETag, sourceIfNoneMatchETag := sourceAccessConditions.pointers()
|
||||
ifAppendPositionEqual, ifMaxSizeLessThanOrEqual := destinationAccessConditions.AppendPositionAccessConditions.pointers()
|
||||
return ab.abClient.AppendBlockFromURL(ctx, sourceURL.String(), 0, httpRange{offset: offset, count: count}.pointers(),
|
||||
transactionalMD5, nil, nil, nil,
|
||||
nil, nil, EncryptionAlgorithmNone, // CPK
|
||||
destinationAccessConditions.LeaseAccessConditions.pointers(),
|
||||
ifMaxSizeLessThanOrEqual, ifAppendPositionEqual,
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, sourceIfModifiedSince, sourceIfUnmodifiedSince, sourceIfMatchETag, sourceIfNoneMatchETag, nil)
|
||||
}
|
||||
|
||||
type AppendBlobAccessConditions struct {
|
||||
ModifiedAccessConditions
|
||||
LeaseAccessConditions
|
||||
AppendPositionAccessConditions
|
||||
}
|
||||
|
||||
// AppendPositionAccessConditions identifies append blob-specific access conditions which you optionally set.
|
||||
type AppendPositionAccessConditions struct {
|
||||
// IfAppendPositionEqual ensures that the AppendBlock operation succeeds
|
||||
// only if the append position is equal to a value.
|
||||
// IfAppendPositionEqual=0 means no 'IfAppendPositionEqual' header specified.
|
||||
// IfAppendPositionEqual>0 means 'IfAppendPositionEqual' header specified with its value
|
||||
// IfAppendPositionEqual==-1 means IfAppendPositionEqual' header specified with a value of 0
|
||||
IfAppendPositionEqual int64
|
||||
|
||||
// IfMaxSizeLessThanOrEqual ensures that the AppendBlock operation succeeds
|
||||
// only if the append blob's size is less than or equal to a value.
|
||||
// IfMaxSizeLessThanOrEqual=0 means no 'IfMaxSizeLessThanOrEqual' header specified.
|
||||
// IfMaxSizeLessThanOrEqual>0 means 'IfMaxSizeLessThanOrEqual' header specified with its value
|
||||
// IfMaxSizeLessThanOrEqual==-1 means 'IfMaxSizeLessThanOrEqual' header specified with a value of 0
|
||||
IfMaxSizeLessThanOrEqual int64
|
||||
}
|
||||
|
||||
// pointers is for internal infrastructure. It returns the fields as pointers.
|
||||
func (ac AppendPositionAccessConditions) pointers() (iape *int64, imsltoe *int64) {
|
||||
var zero int64 // defaults to 0
|
||||
switch ac.IfAppendPositionEqual {
|
||||
case -1:
|
||||
iape = &zero
|
||||
case 0:
|
||||
iape = nil
|
||||
default:
|
||||
iape = &ac.IfAppendPositionEqual
|
||||
}
|
||||
|
||||
switch ac.IfMaxSizeLessThanOrEqual {
|
||||
case -1:
|
||||
imsltoe = &zero
|
||||
case 0:
|
||||
imsltoe = nil
|
||||
default:
|
||||
imsltoe = &ac.IfMaxSizeLessThanOrEqual
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
)
|
||||
|
||||
// A BlobURL represents a URL to an Azure Storage blob; the blob may be a block blob, append blob, or page blob.
|
||||
type BlobURL struct {
|
||||
blobClient blobClient
|
||||
}
|
||||
|
||||
// NewBlobURL creates a BlobURL object using the specified URL and request policy pipeline.
|
||||
func NewBlobURL(url url.URL, p pipeline.Pipeline) BlobURL {
|
||||
blobClient := newBlobClient(url, p)
|
||||
return BlobURL{blobClient: blobClient}
|
||||
}
|
||||
|
||||
// URL returns the URL endpoint used by the BlobURL object.
|
||||
func (b BlobURL) URL() url.URL {
|
||||
return b.blobClient.URL()
|
||||
}
|
||||
|
||||
// String returns the URL as a string.
|
||||
func (b BlobURL) String() string {
|
||||
u := b.URL()
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (b BlobURL) GetAccountInfo(ctx context.Context) (*BlobGetAccountInfoResponse, error) {
|
||||
return b.blobClient.GetAccountInfo(ctx)
|
||||
}
|
||||
|
||||
// WithPipeline creates a new BlobURL object identical to the source but with the specified request policy pipeline.
|
||||
func (b BlobURL) WithPipeline(p pipeline.Pipeline) BlobURL {
|
||||
return NewBlobURL(b.blobClient.URL(), p)
|
||||
}
|
||||
|
||||
// WithSnapshot creates a new BlobURL object identical to the source but with the specified snapshot timestamp.
|
||||
// Pass "" to remove the snapshot returning a URL to the base blob.
|
||||
func (b BlobURL) WithSnapshot(snapshot string) BlobURL {
|
||||
p := NewBlobURLParts(b.URL())
|
||||
p.Snapshot = snapshot
|
||||
return NewBlobURL(p.URL(), b.blobClient.Pipeline())
|
||||
}
|
||||
|
||||
// ToAppendBlobURL creates an AppendBlobURL using the source's URL and pipeline.
|
||||
func (b BlobURL) ToAppendBlobURL() AppendBlobURL {
|
||||
return NewAppendBlobURL(b.URL(), b.blobClient.Pipeline())
|
||||
}
|
||||
|
||||
// ToBlockBlobURL creates a BlockBlobURL using the source's URL and pipeline.
|
||||
func (b BlobURL) ToBlockBlobURL() BlockBlobURL {
|
||||
return NewBlockBlobURL(b.URL(), b.blobClient.Pipeline())
|
||||
}
|
||||
|
||||
// ToPageBlobURL creates a PageBlobURL using the source's URL and pipeline.
|
||||
func (b BlobURL) ToPageBlobURL() PageBlobURL {
|
||||
return NewPageBlobURL(b.URL(), b.blobClient.Pipeline())
|
||||
}
|
||||
|
||||
// DownloadBlob reads a range of bytes from a blob. The response also includes the blob's properties and metadata.
|
||||
// Passing azblob.CountToEnd (0) for count will download the blob from the offset to the end.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/get-blob.
|
||||
func (b BlobURL) Download(ctx context.Context, offset int64, count int64, ac BlobAccessConditions, rangeGetContentMD5 bool) (*DownloadResponse, error) {
|
||||
var xRangeGetContentMD5 *bool
|
||||
if rangeGetContentMD5 {
|
||||
xRangeGetContentMD5 = &rangeGetContentMD5
|
||||
}
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
|
||||
dr, err := b.blobClient.Download(ctx, nil, nil,
|
||||
httpRange{offset: offset, count: count}.pointers(),
|
||||
ac.LeaseAccessConditions.pointers(), xRangeGetContentMD5, nil,
|
||||
nil, nil, EncryptionAlgorithmNone, // CPK
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DownloadResponse{
|
||||
b: b,
|
||||
r: dr,
|
||||
ctx: ctx,
|
||||
getInfo: HTTPGetterInfo{Offset: offset, Count: count, ETag: dr.ETag()},
|
||||
}, err
|
||||
}
|
||||
|
||||
// DeleteBlob marks the specified blob or snapshot for deletion. The blob is later deleted during garbage collection.
|
||||
// Note that deleting a blob also deletes all its snapshots.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/delete-blob.
|
||||
func (b BlobURL) Delete(ctx context.Context, deleteOptions DeleteSnapshotsOptionType, ac BlobAccessConditions) (*BlobDeleteResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
|
||||
return b.blobClient.Delete(ctx, nil, nil, ac.LeaseAccessConditions.pointers(), deleteOptions,
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
|
||||
}
|
||||
|
||||
// Undelete restores the contents and metadata of a soft-deleted blob and any associated soft-deleted snapshots.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/undelete-blob.
|
||||
func (b BlobURL) Undelete(ctx context.Context) (*BlobUndeleteResponse, error) {
|
||||
return b.blobClient.Undelete(ctx, nil, nil)
|
||||
}
|
||||
|
||||
// SetTier operation sets the tier on a blob. The operation is allowed on a page
|
||||
// blob in a premium storage account and on a block blob in a blob storage account (locally
|
||||
// redundant storage only). A premium page blob's tier determines the allowed size, IOPS, and
|
||||
// bandwidth of the blob. A block blob's tier determines Hot/Cool/Archive storage type. This operation
|
||||
// does not update the blob's ETag.
|
||||
// For detailed information about block blob level tiering see https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blob-storage-tiers.
|
||||
func (b BlobURL) SetTier(ctx context.Context, tier AccessTierType, lac LeaseAccessConditions) (*BlobSetTierResponse, error) {
|
||||
return b.blobClient.SetTier(ctx, tier, nil, RehydratePriorityNone, nil, lac.pointers())
|
||||
}
|
||||
|
||||
// GetBlobProperties returns the blob's properties.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/get-blob-properties.
|
||||
func (b BlobURL) GetProperties(ctx context.Context, ac BlobAccessConditions) (*BlobGetPropertiesResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
|
||||
return b.blobClient.GetProperties(ctx, nil, nil, ac.LeaseAccessConditions.pointers(),
|
||||
nil, nil, EncryptionAlgorithmNone, // CPK
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
|
||||
}
|
||||
|
||||
// SetBlobHTTPHeaders changes a blob's HTTP headers.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/set-blob-properties.
|
||||
func (b BlobURL) SetHTTPHeaders(ctx context.Context, h BlobHTTPHeaders, ac BlobAccessConditions) (*BlobSetHTTPHeadersResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
|
||||
return b.blobClient.SetHTTPHeaders(ctx, nil,
|
||||
&h.CacheControl, &h.ContentType, h.ContentMD5, &h.ContentEncoding, &h.ContentLanguage,
|
||||
ac.LeaseAccessConditions.pointers(), ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag,
|
||||
&h.ContentDisposition, nil)
|
||||
}
|
||||
|
||||
// SetBlobMetadata changes a blob's metadata.
|
||||
// https://docs.microsoft.com/rest/api/storageservices/set-blob-metadata.
|
||||
func (b BlobURL) SetMetadata(ctx context.Context, metadata Metadata, ac BlobAccessConditions) (*BlobSetMetadataResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
|
||||
return b.blobClient.SetMetadata(ctx, nil, metadata, ac.LeaseAccessConditions.pointers(),
|
||||
nil, nil, EncryptionAlgorithmNone, // CPK
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
|
||||
}
|
||||
|
||||
// CreateSnapshot creates a read-only snapshot of a blob.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/snapshot-blob.
|
||||
func (b BlobURL) CreateSnapshot(ctx context.Context, metadata Metadata, ac BlobAccessConditions) (*BlobCreateSnapshotResponse, error) {
|
||||
// CreateSnapshot does NOT panic if the user tries to create a snapshot using a URL that already has a snapshot query parameter
|
||||
// because checking this would be a performance hit for a VERY unusual path and I don't think the common case should suffer this
|
||||
// performance hit.
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
|
||||
return b.blobClient.CreateSnapshot(ctx, nil, metadata,
|
||||
nil, nil, EncryptionAlgorithmNone, // CPK
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, ac.LeaseAccessConditions.pointers(), nil)
|
||||
}
|
||||
|
||||
// AcquireLease acquires a lease on the blob for write and delete operations. The lease duration must be between
|
||||
// 15 to 60 seconds, or infinite (-1).
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/lease-blob.
|
||||
func (b BlobURL) AcquireLease(ctx context.Context, proposedID string, duration int32, ac ModifiedAccessConditions) (*BlobAcquireLeaseResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.pointers()
|
||||
return b.blobClient.AcquireLease(ctx, nil, &duration, &proposedID,
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
|
||||
}
|
||||
|
||||
// RenewLease renews the blob's previously-acquired lease.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/lease-blob.
|
||||
func (b BlobURL) RenewLease(ctx context.Context, leaseID string, ac ModifiedAccessConditions) (*BlobRenewLeaseResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.pointers()
|
||||
return b.blobClient.RenewLease(ctx, leaseID, nil,
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
|
||||
}
|
||||
|
||||
// ReleaseLease releases the blob's previously-acquired lease.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/lease-blob.
|
||||
func (b BlobURL) ReleaseLease(ctx context.Context, leaseID string, ac ModifiedAccessConditions) (*BlobReleaseLeaseResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.pointers()
|
||||
return b.blobClient.ReleaseLease(ctx, leaseID, nil,
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
|
||||
}
|
||||
|
||||
// BreakLease breaks the blob's previously-acquired lease (if it exists). Pass the LeaseBreakDefault (-1)
|
||||
// constant to break a fixed-duration lease when it expires or an infinite lease immediately.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/lease-blob.
|
||||
func (b BlobURL) BreakLease(ctx context.Context, breakPeriodInSeconds int32, ac ModifiedAccessConditions) (*BlobBreakLeaseResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.pointers()
|
||||
return b.blobClient.BreakLease(ctx, nil, leasePeriodPointer(breakPeriodInSeconds),
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
|
||||
}
|
||||
|
||||
// ChangeLease changes the blob's lease ID.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/lease-blob.
|
||||
func (b BlobURL) ChangeLease(ctx context.Context, leaseID string, proposedID string, ac ModifiedAccessConditions) (*BlobChangeLeaseResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.pointers()
|
||||
return b.blobClient.ChangeLease(ctx, leaseID, proposedID,
|
||||
nil, ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
|
||||
}
|
||||
|
||||
// LeaseBreakNaturally tells ContainerURL's or BlobURL's BreakLease method to break the lease using service semantics.
|
||||
const LeaseBreakNaturally = -1
|
||||
|
||||
func leasePeriodPointer(period int32) (p *int32) {
|
||||
if period != LeaseBreakNaturally {
|
||||
p = &period
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartCopyFromURL copies the data at the source URL to a blob.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/copy-blob.
|
||||
func (b BlobURL) StartCopyFromURL(ctx context.Context, source url.URL, metadata Metadata, srcac ModifiedAccessConditions, dstac BlobAccessConditions) (*BlobStartCopyFromURLResponse, error) {
|
||||
srcIfModifiedSince, srcIfUnmodifiedSince, srcIfMatchETag, srcIfNoneMatchETag := srcac.pointers()
|
||||
dstIfModifiedSince, dstIfUnmodifiedSince, dstIfMatchETag, dstIfNoneMatchETag := dstac.ModifiedAccessConditions.pointers()
|
||||
dstLeaseID := dstac.LeaseAccessConditions.pointers()
|
||||
|
||||
return b.blobClient.StartCopyFromURL(ctx, source.String(), nil, metadata,
|
||||
AccessTierNone, RehydratePriorityNone, srcIfModifiedSince, srcIfUnmodifiedSince,
|
||||
srcIfMatchETag, srcIfNoneMatchETag,
|
||||
dstIfModifiedSince, dstIfUnmodifiedSince,
|
||||
dstIfMatchETag, dstIfNoneMatchETag,
|
||||
dstLeaseID, nil)
|
||||
}
|
||||
|
||||
// AbortCopyFromURL stops a pending copy that was previously started and leaves a destination blob with 0 length and metadata.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/abort-copy-blob.
|
||||
func (b BlobURL) AbortCopyFromURL(ctx context.Context, copyID string, ac LeaseAccessConditions) (*BlobAbortCopyFromURLResponse, error) {
|
||||
return b.blobClient.AbortCopyFromURL(ctx, copyID, nil, ac.pointers(), nil)
|
||||
}
|
134
vendor/github.com/Azure/azure-storage-blob-go/azblob/url_block_blob.go
generated
vendored
Normal file
134
vendor/github.com/Azure/azure-storage-blob-go/azblob/url_block_blob.go
generated
vendored
Normal file
|
@ -0,0 +1,134 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
)
|
||||
|
||||
const (
|
||||
// BlockBlobMaxUploadBlobBytes indicates the maximum number of bytes that can be sent in a call to Upload.
|
||||
BlockBlobMaxUploadBlobBytes = 256 * 1024 * 1024 // 256MB
|
||||
|
||||
// BlockBlobMaxStageBlockBytes indicates the maximum number of bytes that can be sent in a call to StageBlock.
|
||||
BlockBlobMaxStageBlockBytes = 100 * 1024 * 1024 // 100MB
|
||||
|
||||
// BlockBlobMaxBlocks indicates the maximum number of blocks allowed in a block blob.
|
||||
BlockBlobMaxBlocks = 50000
|
||||
)
|
||||
|
||||
// BlockBlobURL defines a set of operations applicable to block blobs.
|
||||
type BlockBlobURL struct {
|
||||
BlobURL
|
||||
bbClient blockBlobClient
|
||||
}
|
||||
|
||||
// NewBlockBlobURL creates a BlockBlobURL object using the specified URL and request policy pipeline.
|
||||
func NewBlockBlobURL(url url.URL, p pipeline.Pipeline) BlockBlobURL {
|
||||
blobClient := newBlobClient(url, p)
|
||||
bbClient := newBlockBlobClient(url, p)
|
||||
return BlockBlobURL{BlobURL: BlobURL{blobClient: blobClient}, bbClient: bbClient}
|
||||
}
|
||||
|
||||
// WithPipeline creates a new BlockBlobURL object identical to the source but with the specific request policy pipeline.
|
||||
func (bb BlockBlobURL) WithPipeline(p pipeline.Pipeline) BlockBlobURL {
|
||||
return NewBlockBlobURL(bb.blobClient.URL(), p)
|
||||
}
|
||||
|
||||
// WithSnapshot creates a new BlockBlobURL object identical to the source but with the specified snapshot timestamp.
|
||||
// Pass "" to remove the snapshot returning a URL to the base blob.
|
||||
func (bb BlockBlobURL) WithSnapshot(snapshot string) BlockBlobURL {
|
||||
p := NewBlobURLParts(bb.URL())
|
||||
p.Snapshot = snapshot
|
||||
return NewBlockBlobURL(p.URL(), bb.blobClient.Pipeline())
|
||||
}
|
||||
|
||||
func (bb BlockBlobURL) GetAccountInfo(ctx context.Context) (*BlobGetAccountInfoResponse, error) {
|
||||
return bb.blobClient.GetAccountInfo(ctx)
|
||||
}
|
||||
|
||||
// Upload creates a new block blob or overwrites an existing block blob.
|
||||
// Updating an existing block blob overwrites any existing metadata on the blob. Partial updates are not
|
||||
// supported with Upload; the content of the existing blob is overwritten with the new content. To
|
||||
// perform a partial update of a block blob, use StageBlock and CommitBlockList.
|
||||
// This method panics if the stream is not at position 0.
|
||||
// Note that the http client closes the body stream after the request is sent to the service.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/put-blob.
|
||||
func (bb BlockBlobURL) Upload(ctx context.Context, body io.ReadSeeker, h BlobHTTPHeaders, metadata Metadata, ac BlobAccessConditions) (*BlockBlobUploadResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
|
||||
count, err := validateSeekableStreamAt0AndGetCount(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bb.bbClient.Upload(ctx, body, count, nil, nil,
|
||||
&h.ContentType, &h.ContentEncoding, &h.ContentLanguage, h.ContentMD5,
|
||||
&h.CacheControl, metadata, ac.LeaseAccessConditions.pointers(), &h.ContentDisposition,
|
||||
nil, nil, EncryptionAlgorithmNone, // CPK
|
||||
AccessTierNone, ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag,
|
||||
nil)
|
||||
}
|
||||
|
||||
// StageBlock uploads the specified block to the block blob's "staging area" to be later committed by a call to CommitBlockList.
|
||||
// Note that the http client closes the body stream after the request is sent to the service.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/put-block.
|
||||
func (bb BlockBlobURL) StageBlock(ctx context.Context, base64BlockID string, body io.ReadSeeker, ac LeaseAccessConditions, transactionalMD5 []byte) (*BlockBlobStageBlockResponse, error) {
|
||||
count, err := validateSeekableStreamAt0AndGetCount(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bb.bbClient.StageBlock(ctx, base64BlockID, count, body, transactionalMD5, nil, nil, ac.pointers(),
|
||||
nil, nil, EncryptionAlgorithmNone, // CPK
|
||||
nil)
|
||||
}
|
||||
|
||||
// StageBlockFromURL copies the specified block from a source URL to the block blob's "staging area" to be later committed by a call to CommitBlockList.
|
||||
// If count is CountToEnd (0), then data is read from specified offset to the end.
|
||||
// For more information, see https://docs.microsoft.com/en-us/rest/api/storageservices/put-block-from-url.
|
||||
func (bb BlockBlobURL) StageBlockFromURL(ctx context.Context, base64BlockID string, sourceURL url.URL, offset int64, count int64, destinationAccessConditions LeaseAccessConditions, sourceAccessConditions ModifiedAccessConditions) (*BlockBlobStageBlockFromURLResponse, error) {
|
||||
sourceIfModifiedSince, sourceIfUnmodifiedSince, sourceIfMatchETag, sourceIfNoneMatchETag := sourceAccessConditions.pointers()
|
||||
return bb.bbClient.StageBlockFromURL(ctx, base64BlockID, 0, sourceURL.String(), httpRange{offset: offset, count: count}.pointers(), nil, nil, nil,
|
||||
nil, nil, EncryptionAlgorithmNone, // CPK
|
||||
destinationAccessConditions.pointers(), sourceIfModifiedSince, sourceIfUnmodifiedSince, sourceIfMatchETag, sourceIfNoneMatchETag, nil)
|
||||
}
|
||||
|
||||
// CommitBlockList writes a blob by specifying the list of block IDs that make up the blob.
|
||||
// In order to be written as part of a blob, a block must have been successfully written
|
||||
// to the server in a prior PutBlock operation. You can call PutBlockList to update a blob
|
||||
// by uploading only those blocks that have changed, then committing the new and existing
|
||||
// blocks together. Any blocks not specified in the block list and permanently deleted.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/put-block-list.
|
||||
func (bb BlockBlobURL) CommitBlockList(ctx context.Context, base64BlockIDs []string, h BlobHTTPHeaders,
|
||||
metadata Metadata, ac BlobAccessConditions) (*BlockBlobCommitBlockListResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
|
||||
return bb.bbClient.CommitBlockList(ctx, BlockLookupList{Latest: base64BlockIDs}, nil,
|
||||
&h.CacheControl, &h.ContentType, &h.ContentEncoding, &h.ContentLanguage, h.ContentMD5, nil, nil,
|
||||
metadata, ac.LeaseAccessConditions.pointers(), &h.ContentDisposition,
|
||||
nil, nil, EncryptionAlgorithmNone, // CPK
|
||||
AccessTierNone,
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
|
||||
}
|
||||
|
||||
// GetBlockList returns the list of blocks that have been uploaded as part of a block blob using the specified block list filter.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/get-block-list.
|
||||
func (bb BlockBlobURL) GetBlockList(ctx context.Context, listType BlockListType, ac LeaseAccessConditions) (*BlockList, error) {
|
||||
return bb.bbClient.GetBlockList(ctx, listType, nil, nil, ac.pointers(), nil)
|
||||
}
|
||||
|
||||
// CopyFromURL synchronously copies the data at the source URL to a block blob, with sizes up to 256 MB.
|
||||
// For more information, see https://docs.microsoft.com/en-us/rest/api/storageservices/copy-blob-from-url.
|
||||
func (bb BlockBlobURL) CopyFromURL(ctx context.Context, source url.URL, metadata Metadata,
|
||||
srcac ModifiedAccessConditions, dstac BlobAccessConditions, srcContentMD5 []byte) (*BlobCopyFromURLResponse, error) {
|
||||
|
||||
srcIfModifiedSince, srcIfUnmodifiedSince, srcIfMatchETag, srcIfNoneMatchETag := srcac.pointers()
|
||||
dstIfModifiedSince, dstIfUnmodifiedSince, dstIfMatchETag, dstIfNoneMatchETag := dstac.ModifiedAccessConditions.pointers()
|
||||
dstLeaseID := dstac.LeaseAccessConditions.pointers()
|
||||
|
||||
return bb.blobClient.CopyFromURL(ctx, source.String(), nil, metadata, AccessTierNone,
|
||||
srcIfModifiedSince, srcIfUnmodifiedSince,
|
||||
srcIfMatchETag, srcIfNoneMatchETag,
|
||||
dstIfModifiedSince, dstIfUnmodifiedSince,
|
||||
dstIfMatchETag, dstIfNoneMatchETag,
|
||||
dstLeaseID, nil, srcContentMD5)
|
||||
}
|
299
vendor/github.com/Azure/azure-storage-blob-go/azblob/url_container.go
generated
vendored
Normal file
299
vendor/github.com/Azure/azure-storage-blob-go/azblob/url_container.go
generated
vendored
Normal file
|
@ -0,0 +1,299 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
)
|
||||
|
||||
// A ContainerURL represents a URL to the Azure Storage container allowing you to manipulate its blobs.
|
||||
type ContainerURL struct {
|
||||
client containerClient
|
||||
}
|
||||
|
||||
// NewContainerURL creates a ContainerURL object using the specified URL and request policy pipeline.
|
||||
func NewContainerURL(url url.URL, p pipeline.Pipeline) ContainerURL {
|
||||
client := newContainerClient(url, p)
|
||||
return ContainerURL{client: client}
|
||||
}
|
||||
|
||||
// URL returns the URL endpoint used by the ContainerURL object.
|
||||
func (c ContainerURL) URL() url.URL {
|
||||
return c.client.URL()
|
||||
}
|
||||
|
||||
// String returns the URL as a string.
|
||||
func (c ContainerURL) String() string {
|
||||
u := c.URL()
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (c ContainerURL) GetAccountInfo(ctx context.Context) (*ContainerGetAccountInfoResponse, error) {
|
||||
return c.client.GetAccountInfo(ctx)
|
||||
}
|
||||
|
||||
// WithPipeline creates a new ContainerURL object identical to the source but with the specified request policy pipeline.
|
||||
func (c ContainerURL) WithPipeline(p pipeline.Pipeline) ContainerURL {
|
||||
return NewContainerURL(c.URL(), p)
|
||||
}
|
||||
|
||||
// NewBlobURL creates a new BlobURL object by concatenating blobName to the end of
|
||||
// ContainerURL's URL. The new BlobURL uses the same request policy pipeline as the ContainerURL.
|
||||
// To change the pipeline, create the BlobURL and then call its WithPipeline method passing in the
|
||||
// desired pipeline object. Or, call this package's NewBlobURL instead of calling this object's
|
||||
// NewBlobURL method.
|
||||
func (c ContainerURL) NewBlobURL(blobName string) BlobURL {
|
||||
blobURL := appendToURLPath(c.URL(), blobName)
|
||||
return NewBlobURL(blobURL, c.client.Pipeline())
|
||||
}
|
||||
|
||||
// NewAppendBlobURL creates a new AppendBlobURL object by concatenating blobName to the end of
|
||||
// ContainerURL's URL. The new AppendBlobURL uses the same request policy pipeline as the ContainerURL.
|
||||
// To change the pipeline, create the AppendBlobURL and then call its WithPipeline method passing in the
|
||||
// desired pipeline object. Or, call this package's NewAppendBlobURL instead of calling this object's
|
||||
// NewAppendBlobURL method.
|
||||
func (c ContainerURL) NewAppendBlobURL(blobName string) AppendBlobURL {
|
||||
blobURL := appendToURLPath(c.URL(), blobName)
|
||||
return NewAppendBlobURL(blobURL, c.client.Pipeline())
|
||||
}
|
||||
|
||||
// NewBlockBlobURL creates a new BlockBlobURL object by concatenating blobName to the end of
|
||||
// ContainerURL's URL. The new BlockBlobURL uses the same request policy pipeline as the ContainerURL.
|
||||
// To change the pipeline, create the BlockBlobURL and then call its WithPipeline method passing in the
|
||||
// desired pipeline object. Or, call this package's NewBlockBlobURL instead of calling this object's
|
||||
// NewBlockBlobURL method.
|
||||
func (c ContainerURL) NewBlockBlobURL(blobName string) BlockBlobURL {
|
||||
blobURL := appendToURLPath(c.URL(), blobName)
|
||||
return NewBlockBlobURL(blobURL, c.client.Pipeline())
|
||||
}
|
||||
|
||||
// NewPageBlobURL creates a new PageBlobURL object by concatenating blobName to the end of
|
||||
// ContainerURL's URL. The new PageBlobURL uses the same request policy pipeline as the ContainerURL.
|
||||
// To change the pipeline, create the PageBlobURL and then call its WithPipeline method passing in the
|
||||
// desired pipeline object. Or, call this package's NewPageBlobURL instead of calling this object's
|
||||
// NewPageBlobURL method.
|
||||
func (c ContainerURL) NewPageBlobURL(blobName string) PageBlobURL {
|
||||
blobURL := appendToURLPath(c.URL(), blobName)
|
||||
return NewPageBlobURL(blobURL, c.client.Pipeline())
|
||||
}
|
||||
|
||||
// Create creates a new container within a storage account. If a container with the same name already exists, the operation fails.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/create-container.
|
||||
func (c ContainerURL) Create(ctx context.Context, metadata Metadata, publicAccessType PublicAccessType) (*ContainerCreateResponse, error) {
|
||||
return c.client.Create(ctx, nil, metadata, publicAccessType, nil)
|
||||
}
|
||||
|
||||
// Delete marks the specified container for deletion. The container and any blobs contained within it are later deleted during garbage collection.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/delete-container.
|
||||
func (c ContainerURL) Delete(ctx context.Context, ac ContainerAccessConditions) (*ContainerDeleteResponse, error) {
|
||||
if ac.IfMatch != ETagNone || ac.IfNoneMatch != ETagNone {
|
||||
return nil, errors.New("the IfMatch and IfNoneMatch access conditions must have their default values because they are ignored by the service")
|
||||
}
|
||||
|
||||
ifModifiedSince, ifUnmodifiedSince, _, _ := ac.ModifiedAccessConditions.pointers()
|
||||
return c.client.Delete(ctx, nil, ac.LeaseAccessConditions.pointers(),
|
||||
ifModifiedSince, ifUnmodifiedSince, nil)
|
||||
}
|
||||
|
||||
// GetProperties returns the container's properties.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/get-container-metadata.
|
||||
func (c ContainerURL) GetProperties(ctx context.Context, ac LeaseAccessConditions) (*ContainerGetPropertiesResponse, error) {
|
||||
// NOTE: GetMetadata actually calls GetProperties internally because GetProperties returns the metadata AND the properties.
|
||||
// This allows us to not expose a GetProperties method at all simplifying the API.
|
||||
return c.client.GetProperties(ctx, nil, ac.pointers(), nil)
|
||||
}
|
||||
|
||||
// SetMetadata sets the container's metadata.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/set-container-metadata.
|
||||
func (c ContainerURL) SetMetadata(ctx context.Context, metadata Metadata, ac ContainerAccessConditions) (*ContainerSetMetadataResponse, error) {
|
||||
if !ac.IfUnmodifiedSince.IsZero() || ac.IfMatch != ETagNone || ac.IfNoneMatch != ETagNone {
|
||||
return nil, errors.New("the IfUnmodifiedSince, IfMatch, and IfNoneMatch must have their default values because they are ignored by the blob service")
|
||||
}
|
||||
ifModifiedSince, _, _, _ := ac.ModifiedAccessConditions.pointers()
|
||||
return c.client.SetMetadata(ctx, nil, ac.LeaseAccessConditions.pointers(), metadata, ifModifiedSince, nil)
|
||||
}
|
||||
|
||||
// GetAccessPolicy returns the container's access policy. The access policy indicates whether container's blobs may be accessed publicly.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/get-container-acl.
|
||||
func (c ContainerURL) GetAccessPolicy(ctx context.Context, ac LeaseAccessConditions) (*SignedIdentifiers, error) {
|
||||
return c.client.GetAccessPolicy(ctx, nil, ac.pointers(), nil)
|
||||
}
|
||||
|
||||
// The AccessPolicyPermission type simplifies creating the permissions string for a container's access policy.
|
||||
// Initialize an instance of this type and then call its String method to set AccessPolicy's Permission field.
|
||||
type AccessPolicyPermission struct {
|
||||
Read, Add, Create, Write, Delete, List bool
|
||||
}
|
||||
|
||||
// String produces the access policy permission string for an Azure Storage container.
|
||||
// Call this method to set AccessPolicy's Permission field.
|
||||
func (p AccessPolicyPermission) String() string {
|
||||
var b bytes.Buffer
|
||||
if p.Read {
|
||||
b.WriteRune('r')
|
||||
}
|
||||
if p.Add {
|
||||
b.WriteRune('a')
|
||||
}
|
||||
if p.Create {
|
||||
b.WriteRune('c')
|
||||
}
|
||||
if p.Write {
|
||||
b.WriteRune('w')
|
||||
}
|
||||
if p.Delete {
|
||||
b.WriteRune('d')
|
||||
}
|
||||
if p.List {
|
||||
b.WriteRune('l')
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Parse initializes the AccessPolicyPermission's fields from a string.
|
||||
func (p *AccessPolicyPermission) Parse(s string) error {
|
||||
*p = AccessPolicyPermission{} // Clear the flags
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case 'r':
|
||||
p.Read = true
|
||||
case 'a':
|
||||
p.Add = true
|
||||
case 'c':
|
||||
p.Create = true
|
||||
case 'w':
|
||||
p.Write = true
|
||||
case 'd':
|
||||
p.Delete = true
|
||||
case 'l':
|
||||
p.List = true
|
||||
default:
|
||||
return fmt.Errorf("invalid permission: '%v'", r)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAccessPolicy sets the container's permissions. The access policy indicates whether blobs in a container may be accessed publicly.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/set-container-acl.
|
||||
func (c ContainerURL) SetAccessPolicy(ctx context.Context, accessType PublicAccessType, si []SignedIdentifier,
|
||||
ac ContainerAccessConditions) (*ContainerSetAccessPolicyResponse, error) {
|
||||
if ac.IfMatch != ETagNone || ac.IfNoneMatch != ETagNone {
|
||||
return nil, errors.New("the IfMatch and IfNoneMatch access conditions must have their default values because they are ignored by the service")
|
||||
}
|
||||
ifModifiedSince, ifUnmodifiedSince, _, _ := ac.ModifiedAccessConditions.pointers()
|
||||
return c.client.SetAccessPolicy(ctx, si, nil, ac.LeaseAccessConditions.pointers(),
|
||||
accessType, ifModifiedSince, ifUnmodifiedSince, nil)
|
||||
}
|
||||
|
||||
// AcquireLease acquires a lease on the container for delete operations. The lease duration must be between 15 to 60 seconds, or infinite (-1).
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/lease-container.
|
||||
func (c ContainerURL) AcquireLease(ctx context.Context, proposedID string, duration int32, ac ModifiedAccessConditions) (*ContainerAcquireLeaseResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, _, _ := ac.pointers()
|
||||
return c.client.AcquireLease(ctx, nil, &duration, &proposedID,
|
||||
ifModifiedSince, ifUnmodifiedSince, nil)
|
||||
}
|
||||
|
||||
// RenewLease renews the container's previously-acquired lease.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/lease-container.
|
||||
func (c ContainerURL) RenewLease(ctx context.Context, leaseID string, ac ModifiedAccessConditions) (*ContainerRenewLeaseResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, _, _ := ac.pointers()
|
||||
return c.client.RenewLease(ctx, leaseID, nil, ifModifiedSince, ifUnmodifiedSince, nil)
|
||||
}
|
||||
|
||||
// ReleaseLease releases the container's previously-acquired lease.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/lease-container.
|
||||
func (c ContainerURL) ReleaseLease(ctx context.Context, leaseID string, ac ModifiedAccessConditions) (*ContainerReleaseLeaseResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, _, _ := ac.pointers()
|
||||
return c.client.ReleaseLease(ctx, leaseID, nil, ifModifiedSince, ifUnmodifiedSince, nil)
|
||||
}
|
||||
|
||||
// BreakLease breaks the container's previously-acquired lease (if it exists).
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/lease-container.
|
||||
func (c ContainerURL) BreakLease(ctx context.Context, period int32, ac ModifiedAccessConditions) (*ContainerBreakLeaseResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, _, _ := ac.pointers()
|
||||
return c.client.BreakLease(ctx, nil, leasePeriodPointer(period), ifModifiedSince, ifUnmodifiedSince, nil)
|
||||
}
|
||||
|
||||
// ChangeLease changes the container's lease ID.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/lease-container.
|
||||
func (c ContainerURL) ChangeLease(ctx context.Context, leaseID string, proposedID string, ac ModifiedAccessConditions) (*ContainerChangeLeaseResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, _, _ := ac.pointers()
|
||||
return c.client.ChangeLease(ctx, leaseID, proposedID, nil, ifModifiedSince, ifUnmodifiedSince, nil)
|
||||
}
|
||||
|
||||
// ListBlobsFlatSegment returns a single segment of blobs starting from the specified Marker. Use an empty
|
||||
// Marker to start enumeration from the beginning. Blob names are returned in lexicographic order.
|
||||
// After getting a segment, process it, and then call ListBlobsFlatSegment again (passing the the
|
||||
// previously-returned Marker) to get the next segment.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/list-blobs.
|
||||
func (c ContainerURL) ListBlobsFlatSegment(ctx context.Context, marker Marker, o ListBlobsSegmentOptions) (*ListBlobsFlatSegmentResponse, error) {
|
||||
prefix, include, maxResults := o.pointers()
|
||||
return c.client.ListBlobFlatSegment(ctx, prefix, marker.Val, maxResults, include, nil, nil)
|
||||
}
|
||||
|
||||
// ListBlobsHierarchySegment returns a single segment of blobs starting from the specified Marker. Use an empty
|
||||
// Marker to start enumeration from the beginning. Blob names are returned in lexicographic order.
|
||||
// After getting a segment, process it, and then call ListBlobsHierarchicalSegment again (passing the the
|
||||
// previously-returned Marker) to get the next segment.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/list-blobs.
|
||||
func (c ContainerURL) ListBlobsHierarchySegment(ctx context.Context, marker Marker, delimiter string, o ListBlobsSegmentOptions) (*ListBlobsHierarchySegmentResponse, error) {
|
||||
if o.Details.Snapshots {
|
||||
return nil, errors.New("snapshots are not supported in this listing operation")
|
||||
}
|
||||
prefix, include, maxResults := o.pointers()
|
||||
return c.client.ListBlobHierarchySegment(ctx, delimiter, prefix, marker.Val, maxResults, include, nil, nil)
|
||||
}
|
||||
|
||||
// ListBlobsSegmentOptions defines options available when calling ListBlobs.
|
||||
type ListBlobsSegmentOptions struct {
|
||||
Details BlobListingDetails // No IncludeType header is produced if ""
|
||||
Prefix string // No Prefix header is produced if ""
|
||||
|
||||
// SetMaxResults sets the maximum desired results you want the service to return. Note, the
|
||||
// service may return fewer results than requested.
|
||||
// MaxResults=0 means no 'MaxResults' header specified.
|
||||
MaxResults int32
|
||||
}
|
||||
|
||||
func (o *ListBlobsSegmentOptions) pointers() (prefix *string, include []ListBlobsIncludeItemType, maxResults *int32) {
|
||||
if o.Prefix != "" {
|
||||
prefix = &o.Prefix
|
||||
}
|
||||
include = o.Details.slice()
|
||||
if o.MaxResults != 0 {
|
||||
maxResults = &o.MaxResults
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// BlobListingDetails indicates what additional information the service should return with each blob.
|
||||
type BlobListingDetails struct {
|
||||
Copy, Metadata, Snapshots, UncommittedBlobs, Deleted bool
|
||||
}
|
||||
|
||||
// string produces the Include query parameter's value.
|
||||
func (d *BlobListingDetails) slice() []ListBlobsIncludeItemType {
|
||||
items := []ListBlobsIncludeItemType{}
|
||||
// NOTE: Multiple strings MUST be appended in alphabetic order or signing the string for authentication fails!
|
||||
if d.Copy {
|
||||
items = append(items, ListBlobsIncludeItemCopy)
|
||||
}
|
||||
if d.Deleted {
|
||||
items = append(items, ListBlobsIncludeItemDeleted)
|
||||
}
|
||||
if d.Metadata {
|
||||
items = append(items, ListBlobsIncludeItemMetadata)
|
||||
}
|
||||
if d.Snapshots {
|
||||
items = append(items, ListBlobsIncludeItemSnapshots)
|
||||
}
|
||||
if d.UncommittedBlobs {
|
||||
items = append(items, ListBlobsIncludeItemUncommittedblobs)
|
||||
}
|
||||
return items
|
||||
}
|
233
vendor/github.com/Azure/azure-storage-blob-go/azblob/url_page_blob.go
generated
vendored
Normal file
233
vendor/github.com/Azure/azure-storage-blob-go/azblob/url_page_blob.go
generated
vendored
Normal file
|
@ -0,0 +1,233 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
)
|
||||
|
||||
const (
|
||||
// PageBlobPageBytes indicates the number of bytes in a page (512).
|
||||
PageBlobPageBytes = 512
|
||||
|
||||
// PageBlobMaxPutPagesBytes indicates the maximum number of bytes that can be sent in a call to PutPage.
|
||||
PageBlobMaxUploadPagesBytes = 4 * 1024 * 1024 // 4MB
|
||||
)
|
||||
|
||||
// PageBlobURL defines a set of operations applicable to page blobs.
|
||||
type PageBlobURL struct {
|
||||
BlobURL
|
||||
pbClient pageBlobClient
|
||||
}
|
||||
|
||||
// NewPageBlobURL creates a PageBlobURL object using the specified URL and request policy pipeline.
|
||||
func NewPageBlobURL(url url.URL, p pipeline.Pipeline) PageBlobURL {
|
||||
blobClient := newBlobClient(url, p)
|
||||
pbClient := newPageBlobClient(url, p)
|
||||
return PageBlobURL{BlobURL: BlobURL{blobClient: blobClient}, pbClient: pbClient}
|
||||
}
|
||||
|
||||
// WithPipeline creates a new PageBlobURL object identical to the source but with the specific request policy pipeline.
|
||||
func (pb PageBlobURL) WithPipeline(p pipeline.Pipeline) PageBlobURL {
|
||||
return NewPageBlobURL(pb.blobClient.URL(), p)
|
||||
}
|
||||
|
||||
// WithSnapshot creates a new PageBlobURL object identical to the source but with the specified snapshot timestamp.
|
||||
// Pass "" to remove the snapshot returning a URL to the base blob.
|
||||
func (pb PageBlobURL) WithSnapshot(snapshot string) PageBlobURL {
|
||||
p := NewBlobURLParts(pb.URL())
|
||||
p.Snapshot = snapshot
|
||||
return NewPageBlobURL(p.URL(), pb.blobClient.Pipeline())
|
||||
}
|
||||
|
||||
func (pb PageBlobURL) GetAccountInfo(ctx context.Context) (*BlobGetAccountInfoResponse, error) {
|
||||
return pb.blobClient.GetAccountInfo(ctx)
|
||||
}
|
||||
|
||||
// Create creates a page blob of the specified length. Call PutPage to upload data data to a page blob.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/put-blob.
|
||||
func (pb PageBlobURL) Create(ctx context.Context, size int64, sequenceNumber int64, h BlobHTTPHeaders, metadata Metadata, ac BlobAccessConditions) (*PageBlobCreateResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
|
||||
return pb.pbClient.Create(ctx, 0, size, nil, PremiumPageBlobAccessTierNone,
|
||||
&h.ContentType, &h.ContentEncoding, &h.ContentLanguage, h.ContentMD5, &h.CacheControl,
|
||||
metadata, ac.LeaseAccessConditions.pointers(), &h.ContentDisposition,
|
||||
nil, nil, EncryptionAlgorithmNone, // CPK
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, &sequenceNumber, nil)
|
||||
}
|
||||
|
||||
// UploadPages writes 1 or more pages to the page blob. The start offset and the stream size must be a multiple of 512 bytes.
|
||||
// This method panics if the stream is not at position 0.
|
||||
// Note that the http client closes the body stream after the request is sent to the service.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/put-page.
|
||||
func (pb PageBlobURL) UploadPages(ctx context.Context, offset int64, body io.ReadSeeker, ac PageBlobAccessConditions, transactionalMD5 []byte) (*PageBlobUploadPagesResponse, error) {
|
||||
count, err := validateSeekableStreamAt0AndGetCount(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
|
||||
ifSequenceNumberLessThanOrEqual, ifSequenceNumberLessThan, ifSequenceNumberEqual := ac.SequenceNumberAccessConditions.pointers()
|
||||
return pb.pbClient.UploadPages(ctx, body, count, transactionalMD5, nil, nil,
|
||||
PageRange{Start: offset, End: offset + count - 1}.pointers(),
|
||||
ac.LeaseAccessConditions.pointers(),
|
||||
nil, nil, EncryptionAlgorithmNone, // CPK
|
||||
ifSequenceNumberLessThanOrEqual, ifSequenceNumberLessThan, ifSequenceNumberEqual,
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
|
||||
}
|
||||
|
||||
// UploadPagesFromURL copies 1 or more pages from a source URL to the page blob.
|
||||
// The sourceOffset specifies the start offset of source data to copy from.
|
||||
// The destOffset specifies the start offset of data in page blob will be written to.
|
||||
// The count must be a multiple of 512 bytes.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/put-page-from-url.
|
||||
func (pb PageBlobURL) UploadPagesFromURL(ctx context.Context, sourceURL url.URL, sourceOffset int64, destOffset int64, count int64, transactionalMD5 []byte, destinationAccessConditions PageBlobAccessConditions, sourceAccessConditions ModifiedAccessConditions) (*PageBlobUploadPagesFromURLResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := destinationAccessConditions.ModifiedAccessConditions.pointers()
|
||||
sourceIfModifiedSince, sourceIfUnmodifiedSince, sourceIfMatchETag, sourceIfNoneMatchETag := sourceAccessConditions.pointers()
|
||||
ifSequenceNumberLessThanOrEqual, ifSequenceNumberLessThan, ifSequenceNumberEqual := destinationAccessConditions.SequenceNumberAccessConditions.pointers()
|
||||
return pb.pbClient.UploadPagesFromURL(ctx, sourceURL.String(), *PageRange{Start: sourceOffset, End: sourceOffset + count - 1}.pointers(), 0,
|
||||
*PageRange{Start: destOffset, End: destOffset + count - 1}.pointers(), transactionalMD5, nil, nil,
|
||||
nil, nil, EncryptionAlgorithmNone, // CPK
|
||||
destinationAccessConditions.LeaseAccessConditions.pointers(),
|
||||
ifSequenceNumberLessThanOrEqual, ifSequenceNumberLessThan, ifSequenceNumberEqual,
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, sourceIfModifiedSince, sourceIfUnmodifiedSince, sourceIfMatchETag, sourceIfNoneMatchETag, nil)
|
||||
}
|
||||
|
||||
// ClearPages frees the specified pages from the page blob.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/put-page.
|
||||
func (pb PageBlobURL) ClearPages(ctx context.Context, offset int64, count int64, ac PageBlobAccessConditions) (*PageBlobClearPagesResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
|
||||
ifSequenceNumberLessThanOrEqual, ifSequenceNumberLessThan, ifSequenceNumberEqual := ac.SequenceNumberAccessConditions.pointers()
|
||||
return pb.pbClient.ClearPages(ctx, 0, nil,
|
||||
PageRange{Start: offset, End: offset + count - 1}.pointers(),
|
||||
ac.LeaseAccessConditions.pointers(),
|
||||
nil, nil, EncryptionAlgorithmNone, // CPK
|
||||
ifSequenceNumberLessThanOrEqual, ifSequenceNumberLessThan,
|
||||
ifSequenceNumberEqual, ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
|
||||
}
|
||||
|
||||
// GetPageRanges returns the list of valid page ranges for a page blob or snapshot of a page blob.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/get-page-ranges.
|
||||
func (pb PageBlobURL) GetPageRanges(ctx context.Context, offset int64, count int64, ac BlobAccessConditions) (*PageList, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
|
||||
return pb.pbClient.GetPageRanges(ctx, nil, nil,
|
||||
httpRange{offset: offset, count: count}.pointers(),
|
||||
ac.LeaseAccessConditions.pointers(),
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
|
||||
}
|
||||
|
||||
// GetPageRangesDiff gets the collection of page ranges that differ between a specified snapshot and this page blob.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/get-page-ranges.
|
||||
func (pb PageBlobURL) GetPageRangesDiff(ctx context.Context, offset int64, count int64, prevSnapshot string, ac BlobAccessConditions) (*PageList, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
|
||||
return pb.pbClient.GetPageRangesDiff(ctx, nil, nil, &prevSnapshot,
|
||||
httpRange{offset: offset, count: count}.pointers(),
|
||||
ac.LeaseAccessConditions.pointers(),
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag,
|
||||
nil)
|
||||
}
|
||||
|
||||
// Resize resizes the page blob to the specified size (which must be a multiple of 512).
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/set-blob-properties.
|
||||
func (pb PageBlobURL) Resize(ctx context.Context, size int64, ac BlobAccessConditions) (*PageBlobResizeResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
|
||||
return pb.pbClient.Resize(ctx, size, nil, ac.LeaseAccessConditions.pointers(),
|
||||
nil, nil, EncryptionAlgorithmNone, // CPK
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
|
||||
}
|
||||
|
||||
// SetSequenceNumber sets the page blob's sequence number.
|
||||
func (pb PageBlobURL) UpdateSequenceNumber(ctx context.Context, action SequenceNumberActionType, sequenceNumber int64,
|
||||
ac BlobAccessConditions) (*PageBlobUpdateSequenceNumberResponse, error) {
|
||||
sn := &sequenceNumber
|
||||
if action == SequenceNumberActionIncrement {
|
||||
sn = nil
|
||||
}
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch := ac.ModifiedAccessConditions.pointers()
|
||||
return pb.pbClient.UpdateSequenceNumber(ctx, action, nil,
|
||||
ac.LeaseAccessConditions.pointers(), ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch,
|
||||
sn, nil)
|
||||
}
|
||||
|
||||
// StartIncrementalCopy begins an operation to start an incremental copy from one page blob's snapshot to this page blob.
|
||||
// The snapshot is copied such that only the differential changes between the previously copied snapshot are transferred to the destination.
|
||||
// The copied snapshots are complete copies of the original snapshot and can be read or copied from as usual.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/incremental-copy-blob and
|
||||
// https://docs.microsoft.com/en-us/azure/virtual-machines/windows/incremental-snapshots.
|
||||
func (pb PageBlobURL) StartCopyIncremental(ctx context.Context, source url.URL, snapshot string, ac BlobAccessConditions) (*PageBlobCopyIncrementalResponse, error) {
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
|
||||
qp := source.Query()
|
||||
qp.Set("snapshot", snapshot)
|
||||
source.RawQuery = qp.Encode()
|
||||
return pb.pbClient.CopyIncremental(ctx, source.String(), nil,
|
||||
ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
|
||||
}
|
||||
|
||||
func (pr PageRange) pointers() *string {
|
||||
endOffset := strconv.FormatInt(int64(pr.End), 10)
|
||||
asString := fmt.Sprintf("bytes=%v-%s", pr.Start, endOffset)
|
||||
return &asString
|
||||
}
|
||||
|
||||
type PageBlobAccessConditions struct {
|
||||
ModifiedAccessConditions
|
||||
LeaseAccessConditions
|
||||
SequenceNumberAccessConditions
|
||||
}
|
||||
|
||||
// SequenceNumberAccessConditions identifies page blob-specific access conditions which you optionally set.
|
||||
type SequenceNumberAccessConditions struct {
|
||||
// IfSequenceNumberLessThan ensures that the page blob operation succeeds
|
||||
// only if the blob's sequence number is less than a value.
|
||||
// IfSequenceNumberLessThan=0 means no 'IfSequenceNumberLessThan' header specified.
|
||||
// IfSequenceNumberLessThan>0 means 'IfSequenceNumberLessThan' header specified with its value
|
||||
// IfSequenceNumberLessThan==-1 means 'IfSequenceNumberLessThan' header specified with a value of 0
|
||||
IfSequenceNumberLessThan int64
|
||||
|
||||
// IfSequenceNumberLessThanOrEqual ensures that the page blob operation succeeds
|
||||
// only if the blob's sequence number is less than or equal to a value.
|
||||
// IfSequenceNumberLessThanOrEqual=0 means no 'IfSequenceNumberLessThanOrEqual' header specified.
|
||||
// IfSequenceNumberLessThanOrEqual>0 means 'IfSequenceNumberLessThanOrEqual' header specified with its value
|
||||
// IfSequenceNumberLessThanOrEqual=-1 means 'IfSequenceNumberLessThanOrEqual' header specified with a value of 0
|
||||
IfSequenceNumberLessThanOrEqual int64
|
||||
|
||||
// IfSequenceNumberEqual ensures that the page blob operation succeeds
|
||||
// only if the blob's sequence number is equal to a value.
|
||||
// IfSequenceNumberEqual=0 means no 'IfSequenceNumberEqual' header specified.
|
||||
// IfSequenceNumberEqual>0 means 'IfSequenceNumberEqual' header specified with its value
|
||||
// IfSequenceNumberEqual=-1 means 'IfSequenceNumberEqual' header specified with a value of 0
|
||||
IfSequenceNumberEqual int64
|
||||
}
|
||||
|
||||
// pointers is for internal infrastructure. It returns the fields as pointers.
|
||||
func (ac SequenceNumberAccessConditions) pointers() (snltoe *int64, snlt *int64, sne *int64) {
|
||||
var zero int64 // Defaults to 0
|
||||
switch ac.IfSequenceNumberLessThan {
|
||||
case -1:
|
||||
snlt = &zero
|
||||
case 0:
|
||||
snlt = nil
|
||||
default:
|
||||
snlt = &ac.IfSequenceNumberLessThan
|
||||
}
|
||||
|
||||
switch ac.IfSequenceNumberLessThanOrEqual {
|
||||
case -1:
|
||||
snltoe = &zero
|
||||
case 0:
|
||||
snltoe = nil
|
||||
default:
|
||||
snltoe = &ac.IfSequenceNumberLessThanOrEqual
|
||||
}
|
||||
switch ac.IfSequenceNumberEqual {
|
||||
case -1:
|
||||
sne = &zero
|
||||
case 0:
|
||||
sne = nil
|
||||
default:
|
||||
sne = &ac.IfSequenceNumberEqual
|
||||
}
|
||||
return
|
||||
}
|
159
vendor/github.com/Azure/azure-storage-blob-go/azblob/url_service.go
generated
vendored
Normal file
159
vendor/github.com/Azure/azure-storage-blob-go/azblob/url_service.go
generated
vendored
Normal file
|
@ -0,0 +1,159 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
)
|
||||
|
||||
const (
|
||||
// ContainerNameRoot is the special Azure Storage name used to identify a storage account's root container.
|
||||
ContainerNameRoot = "$root"
|
||||
|
||||
// ContainerNameLogs is the special Azure Storage name used to identify a storage account's logs container.
|
||||
ContainerNameLogs = "$logs"
|
||||
)
|
||||
|
||||
// A ServiceURL represents a URL to the Azure Storage Blob service allowing you to manipulate blob containers.
|
||||
type ServiceURL struct {
|
||||
client serviceClient
|
||||
}
|
||||
|
||||
// NewServiceURL creates a ServiceURL object using the specified URL and request policy pipeline.
|
||||
func NewServiceURL(primaryURL url.URL, p pipeline.Pipeline) ServiceURL {
|
||||
client := newServiceClient(primaryURL, p)
|
||||
return ServiceURL{client: client}
|
||||
}
|
||||
|
||||
//GetUserDelegationCredential obtains a UserDelegationKey object using the base ServiceURL object.
|
||||
//OAuth is required for this call, as well as any role that can delegate access to the storage account.
|
||||
func (s ServiceURL) GetUserDelegationCredential(ctx context.Context, info KeyInfo, timeout *int32, requestID *string) (UserDelegationCredential, error) {
|
||||
sc := newServiceClient(s.client.url, s.client.p)
|
||||
udk, err := sc.GetUserDelegationKey(ctx, info, timeout, requestID)
|
||||
if err != nil {
|
||||
return UserDelegationCredential{}, err
|
||||
}
|
||||
return NewUserDelegationCredential(strings.Split(s.client.url.Host, ".")[0], *udk), nil
|
||||
}
|
||||
|
||||
//TODO this was supposed to be generated
|
||||
//NewKeyInfo creates a new KeyInfo struct with the correct time formatting & conversion
|
||||
func NewKeyInfo(Start, Expiry time.Time) KeyInfo {
|
||||
return KeyInfo{
|
||||
Start: Start.UTC().Format(SASTimeFormat),
|
||||
Expiry: Expiry.UTC().Format(SASTimeFormat),
|
||||
}
|
||||
}
|
||||
|
||||
func (s ServiceURL) GetAccountInfo(ctx context.Context) (*ServiceGetAccountInfoResponse, error) {
|
||||
return s.client.GetAccountInfo(ctx)
|
||||
}
|
||||
|
||||
// URL returns the URL endpoint used by the ServiceURL object.
|
||||
func (s ServiceURL) URL() url.URL {
|
||||
return s.client.URL()
|
||||
}
|
||||
|
||||
// String returns the URL as a string.
|
||||
func (s ServiceURL) String() string {
|
||||
u := s.URL()
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// WithPipeline creates a new ServiceURL object identical to the source but with the specified request policy pipeline.
|
||||
func (s ServiceURL) WithPipeline(p pipeline.Pipeline) ServiceURL {
|
||||
return NewServiceURL(s.URL(), p)
|
||||
}
|
||||
|
||||
// NewContainerURL creates a new ContainerURL object by concatenating containerName to the end of
|
||||
// ServiceURL's URL. The new ContainerURL uses the same request policy pipeline as the ServiceURL.
|
||||
// To change the pipeline, create the ContainerURL and then call its WithPipeline method passing in the
|
||||
// desired pipeline object. Or, call this package's NewContainerURL instead of calling this object's
|
||||
// NewContainerURL method.
|
||||
func (s ServiceURL) NewContainerURL(containerName string) ContainerURL {
|
||||
containerURL := appendToURLPath(s.URL(), containerName)
|
||||
return NewContainerURL(containerURL, s.client.Pipeline())
|
||||
}
|
||||
|
||||
// appendToURLPath appends a string to the end of a URL's path (prefixing the string with a '/' if required)
|
||||
func appendToURLPath(u url.URL, name string) url.URL {
|
||||
// e.g. "https://ms.com/a/b/?k1=v1&k2=v2#f"
|
||||
// When you call url.Parse() this is what you'll get:
|
||||
// Scheme: "https"
|
||||
// Opaque: ""
|
||||
// User: nil
|
||||
// Host: "ms.com"
|
||||
// Path: "/a/b/" This should start with a / and it might or might not have a trailing slash
|
||||
// RawPath: ""
|
||||
// ForceQuery: false
|
||||
// RawQuery: "k1=v1&k2=v2"
|
||||
// Fragment: "f"
|
||||
if len(u.Path) == 0 || u.Path[len(u.Path)-1] != '/' {
|
||||
u.Path += "/" // Append "/" to end before appending name
|
||||
}
|
||||
u.Path += name
|
||||
return u
|
||||
}
|
||||
|
||||
// ListContainersFlatSegment returns a single segment of containers starting from the specified Marker. Use an empty
|
||||
// Marker to start enumeration from the beginning. Container names are returned in lexicographic order.
|
||||
// After getting a segment, process it, and then call ListContainersFlatSegment again (passing the the
|
||||
// previously-returned Marker) to get the next segment. For more information, see
|
||||
// https://docs.microsoft.com/rest/api/storageservices/list-containers2.
|
||||
func (s ServiceURL) ListContainersSegment(ctx context.Context, marker Marker, o ListContainersSegmentOptions) (*ListContainersSegmentResponse, error) {
|
||||
prefix, include, maxResults := o.pointers()
|
||||
return s.client.ListContainersSegment(ctx, prefix, marker.Val, maxResults, include, nil, nil)
|
||||
}
|
||||
|
||||
// ListContainersOptions defines options available when calling ListContainers.
|
||||
type ListContainersSegmentOptions struct {
|
||||
Detail ListContainersDetail // No IncludeType header is produced if ""
|
||||
Prefix string // No Prefix header is produced if ""
|
||||
MaxResults int32 // 0 means unspecified
|
||||
// TODO: update swagger to generate this type?
|
||||
}
|
||||
|
||||
func (o *ListContainersSegmentOptions) pointers() (prefix *string, include ListContainersIncludeType, maxResults *int32) {
|
||||
if o.Prefix != "" {
|
||||
prefix = &o.Prefix
|
||||
}
|
||||
if o.MaxResults != 0 {
|
||||
maxResults = &o.MaxResults
|
||||
}
|
||||
include = ListContainersIncludeType(o.Detail.string())
|
||||
return
|
||||
}
|
||||
|
||||
// ListContainersFlatDetail indicates what additional information the service should return with each container.
|
||||
type ListContainersDetail struct {
|
||||
// Tells the service whether to return metadata for each container.
|
||||
Metadata bool
|
||||
}
|
||||
|
||||
// string produces the Include query parameter's value.
|
||||
func (d *ListContainersDetail) string() string {
|
||||
items := make([]string, 0, 1)
|
||||
// NOTE: Multiple strings MUST be appended in alphabetic order or signing the string for authentication fails!
|
||||
if d.Metadata {
|
||||
items = append(items, string(ListContainersIncludeMetadata))
|
||||
}
|
||||
if len(items) > 0 {
|
||||
return strings.Join(items, ",")
|
||||
}
|
||||
return string(ListContainersIncludeNone)
|
||||
}
|
||||
|
||||
func (bsu ServiceURL) GetProperties(ctx context.Context) (*StorageServiceProperties, error) {
|
||||
return bsu.client.GetProperties(ctx, nil, nil)
|
||||
}
|
||||
|
||||
func (bsu ServiceURL) SetProperties(ctx context.Context, properties StorageServiceProperties) (*ServiceSetPropertiesResponse, error) {
|
||||
return bsu.client.SetProperties(ctx, properties, nil, nil)
|
||||
}
|
||||
|
||||
func (bsu ServiceURL) GetStatistics(ctx context.Context) (*StorageServiceStats, error) {
|
||||
return bsu.client.GetStatistics(ctx, nil, nil)
|
||||
}
|
38
vendor/github.com/Azure/azure-storage-blob-go/azblob/user_delegation_credential.go
generated
vendored
Normal file
38
vendor/github.com/Azure/azure-storage-blob-go/azblob/user_delegation_credential.go
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
// NewUserDelegationCredential creates a new UserDelegationCredential using a Storage account's name and a user delegation key from it
|
||||
func NewUserDelegationCredential(accountName string, key UserDelegationKey) UserDelegationCredential {
|
||||
return UserDelegationCredential{
|
||||
accountName: accountName,
|
||||
accountKey: key,
|
||||
}
|
||||
}
|
||||
|
||||
type UserDelegationCredential struct {
|
||||
accountName string
|
||||
accountKey UserDelegationKey
|
||||
}
|
||||
|
||||
// AccountName returns the Storage account's name
|
||||
func (f UserDelegationCredential) AccountName() string {
|
||||
return f.accountName
|
||||
}
|
||||
|
||||
// ComputeHMAC
|
||||
func (f UserDelegationCredential) ComputeHMACSHA256(message string) (base64String string) {
|
||||
bytes, _ := base64.StdEncoding.DecodeString(f.accountKey.Value)
|
||||
h := hmac.New(sha256.New, bytes)
|
||||
h.Write([]byte(message))
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// Private method to return important parameters for NewSASQueryParameters
|
||||
func (f UserDelegationCredential) getUDKParams() *UserDelegationKey {
|
||||
return &f.accountKey
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package azblob
|
||||
|
||||
const serviceLibVersion = "0.10"
|
55
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_credential_anonymous.go
generated
vendored
Normal file
55
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_credential_anonymous.go
generated
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
)
|
||||
|
||||
// Credential represent any credential type; it is used to create a credential policy Factory.
|
||||
type Credential interface {
|
||||
pipeline.Factory
|
||||
credentialMarker()
|
||||
}
|
||||
|
||||
type credentialFunc pipeline.FactoryFunc
|
||||
|
||||
func (f credentialFunc) New(next pipeline.Policy, po *pipeline.PolicyOptions) pipeline.Policy {
|
||||
return f(next, po)
|
||||
}
|
||||
|
||||
// credentialMarker is a package-internal method that exists just to satisfy the Credential interface.
|
||||
func (credentialFunc) credentialMarker() {}
|
||||
|
||||
//////////////////////////////
|
||||
|
||||
// NewAnonymousCredential creates an anonymous credential for use with HTTP(S) requests that read public resource
|
||||
// or for use with Shared Access Signatures (SAS).
|
||||
func NewAnonymousCredential() Credential {
|
||||
return anonymousCredentialFactory
|
||||
}
|
||||
|
||||
var anonymousCredentialFactory Credential = &anonymousCredentialPolicyFactory{} // Singleton
|
||||
|
||||
// anonymousCredentialPolicyFactory is the credential's policy factory.
|
||||
type anonymousCredentialPolicyFactory struct {
|
||||
}
|
||||
|
||||
// New creates a credential policy object.
|
||||
func (f *anonymousCredentialPolicyFactory) New(next pipeline.Policy, po *pipeline.PolicyOptions) pipeline.Policy {
|
||||
return &anonymousCredentialPolicy{next: next}
|
||||
}
|
||||
|
||||
// credentialMarker is a package-internal method that exists just to satisfy the Credential interface.
|
||||
func (*anonymousCredentialPolicyFactory) credentialMarker() {}
|
||||
|
||||
// anonymousCredentialPolicy is the credential's policy object.
|
||||
type anonymousCredentialPolicy struct {
|
||||
next pipeline.Policy
|
||||
}
|
||||
|
||||
// Do implements the credential's policy interface.
|
||||
func (p anonymousCredentialPolicy) Do(ctx context.Context, request pipeline.Request) (pipeline.Response, error) {
|
||||
// For anonymous credentials, this is effectively a no-op
|
||||
return p.next.Do(ctx, request)
|
||||
}
|
205
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_credential_shared_key.go
generated
vendored
Normal file
205
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_credential_shared_key.go
generated
vendored
Normal file
|
@ -0,0 +1,205 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
)
|
||||
|
||||
// NewSharedKeyCredential creates an immutable SharedKeyCredential containing the
|
||||
// storage account's name and either its primary or secondary key.
|
||||
func NewSharedKeyCredential(accountName, accountKey string) (*SharedKeyCredential, error) {
|
||||
bytes, err := base64.StdEncoding.DecodeString(accountKey)
|
||||
if err != nil {
|
||||
return &SharedKeyCredential{}, err
|
||||
}
|
||||
return &SharedKeyCredential{accountName: accountName, accountKey: bytes}, nil
|
||||
}
|
||||
|
||||
// SharedKeyCredential contains an account's name and its primary or secondary key.
|
||||
// It is immutable making it shareable and goroutine-safe.
|
||||
type SharedKeyCredential struct {
|
||||
// Only the NewSharedKeyCredential method should set these; all other methods should treat them as read-only
|
||||
accountName string
|
||||
accountKey []byte
|
||||
}
|
||||
|
||||
// AccountName returns the Storage account's name.
|
||||
func (f SharedKeyCredential) AccountName() string {
|
||||
return f.accountName
|
||||
}
|
||||
|
||||
func (f SharedKeyCredential) getAccountKey() []byte {
|
||||
return f.accountKey
|
||||
}
|
||||
|
||||
// noop function to satisfy StorageAccountCredential interface
|
||||
func (f SharedKeyCredential) getUDKParams() *UserDelegationKey {
|
||||
return nil
|
||||
}
|
||||
|
||||
// New creates a credential policy object.
|
||||
func (f *SharedKeyCredential) New(next pipeline.Policy, po *pipeline.PolicyOptions) pipeline.Policy {
|
||||
return pipeline.PolicyFunc(func(ctx context.Context, request pipeline.Request) (pipeline.Response, error) {
|
||||
// Add a x-ms-date header if it doesn't already exist
|
||||
if d := request.Header.Get(headerXmsDate); d == "" {
|
||||
request.Header[headerXmsDate] = []string{time.Now().UTC().Format(http.TimeFormat)}
|
||||
}
|
||||
stringToSign, err := f.buildStringToSign(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signature := f.ComputeHMACSHA256(stringToSign)
|
||||
authHeader := strings.Join([]string{"SharedKey ", f.accountName, ":", signature}, "")
|
||||
request.Header[headerAuthorization] = []string{authHeader}
|
||||
|
||||
response, err := next.Do(ctx, request)
|
||||
if err != nil && response != nil && response.Response() != nil && response.Response().StatusCode == http.StatusForbidden {
|
||||
// Service failed to authenticate request, log it
|
||||
po.Log(pipeline.LogError, "===== HTTP Forbidden status, String-to-Sign:\n"+stringToSign+"\n===============================\n")
|
||||
}
|
||||
return response, err
|
||||
})
|
||||
}
|
||||
|
||||
// credentialMarker is a package-internal method that exists just to satisfy the Credential interface.
|
||||
func (*SharedKeyCredential) credentialMarker() {}
|
||||
|
||||
// Constants ensuring that header names are correctly spelled and consistently cased.
|
||||
const (
|
||||
headerAuthorization = "Authorization"
|
||||
headerCacheControl = "Cache-Control"
|
||||
headerContentEncoding = "Content-Encoding"
|
||||
headerContentDisposition = "Content-Disposition"
|
||||
headerContentLanguage = "Content-Language"
|
||||
headerContentLength = "Content-Length"
|
||||
headerContentMD5 = "Content-MD5"
|
||||
headerContentType = "Content-Type"
|
||||
headerDate = "Date"
|
||||
headerIfMatch = "If-Match"
|
||||
headerIfModifiedSince = "If-Modified-Since"
|
||||
headerIfNoneMatch = "If-None-Match"
|
||||
headerIfUnmodifiedSince = "If-Unmodified-Since"
|
||||
headerRange = "Range"
|
||||
headerUserAgent = "User-Agent"
|
||||
headerXmsDate = "x-ms-date"
|
||||
headerXmsVersion = "x-ms-version"
|
||||
)
|
||||
|
||||
// ComputeHMACSHA256 generates a hash signature for an HTTP request or for a SAS.
|
||||
func (f SharedKeyCredential) ComputeHMACSHA256(message string) (base64String string) {
|
||||
h := hmac.New(sha256.New, f.accountKey)
|
||||
h.Write([]byte(message))
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func (f *SharedKeyCredential) buildStringToSign(request pipeline.Request) (string, error) {
|
||||
// https://docs.microsoft.com/en-us/rest/api/storageservices/authentication-for-the-azure-storage-services
|
||||
headers := request.Header
|
||||
contentLength := headers.Get(headerContentLength)
|
||||
if contentLength == "0" {
|
||||
contentLength = ""
|
||||
}
|
||||
|
||||
canonicalizedResource, err := f.buildCanonicalizedResource(request.URL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
stringToSign := strings.Join([]string{
|
||||
request.Method,
|
||||
headers.Get(headerContentEncoding),
|
||||
headers.Get(headerContentLanguage),
|
||||
contentLength,
|
||||
headers.Get(headerContentMD5),
|
||||
headers.Get(headerContentType),
|
||||
"", // Empty date because x-ms-date is expected (as per web page above)
|
||||
headers.Get(headerIfModifiedSince),
|
||||
headers.Get(headerIfMatch),
|
||||
headers.Get(headerIfNoneMatch),
|
||||
headers.Get(headerIfUnmodifiedSince),
|
||||
headers.Get(headerRange),
|
||||
buildCanonicalizedHeader(headers),
|
||||
canonicalizedResource,
|
||||
}, "\n")
|
||||
return stringToSign, nil
|
||||
}
|
||||
|
||||
func buildCanonicalizedHeader(headers http.Header) string {
|
||||
cm := map[string][]string{}
|
||||
for k, v := range headers {
|
||||
headerName := strings.TrimSpace(strings.ToLower(k))
|
||||
if strings.HasPrefix(headerName, "x-ms-") {
|
||||
cm[headerName] = v // NOTE: the value must not have any whitespace around it.
|
||||
}
|
||||
}
|
||||
if len(cm) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(cm))
|
||||
for key := range cm {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
ch := bytes.NewBufferString("")
|
||||
for i, key := range keys {
|
||||
if i > 0 {
|
||||
ch.WriteRune('\n')
|
||||
}
|
||||
ch.WriteString(key)
|
||||
ch.WriteRune(':')
|
||||
ch.WriteString(strings.Join(cm[key], ","))
|
||||
}
|
||||
return string(ch.Bytes())
|
||||
}
|
||||
|
||||
func (f *SharedKeyCredential) buildCanonicalizedResource(u *url.URL) (string, error) {
|
||||
// https://docs.microsoft.com/en-us/rest/api/storageservices/authentication-for-the-azure-storage-services
|
||||
cr := bytes.NewBufferString("/")
|
||||
cr.WriteString(f.accountName)
|
||||
|
||||
if len(u.Path) > 0 {
|
||||
// Any portion of the CanonicalizedResource string that is derived from
|
||||
// the resource's URI should be encoded exactly as it is in the URI.
|
||||
// -- https://msdn.microsoft.com/en-gb/library/azure/dd179428.aspx
|
||||
cr.WriteString(u.EscapedPath())
|
||||
} else {
|
||||
// a slash is required to indicate the root path
|
||||
cr.WriteString("/")
|
||||
}
|
||||
|
||||
// params is a map[string][]string; param name is key; params values is []string
|
||||
params, err := url.ParseQuery(u.RawQuery) // Returns URL decoded values
|
||||
if err != nil {
|
||||
return "", errors.New("parsing query parameters must succeed, otherwise there might be serious problems in the SDK/generated code")
|
||||
}
|
||||
|
||||
if len(params) > 0 { // There is at least 1 query parameter
|
||||
paramNames := []string{} // We use this to sort the parameter key names
|
||||
for paramName := range params {
|
||||
paramNames = append(paramNames, paramName) // paramNames must be lowercase
|
||||
}
|
||||
sort.Strings(paramNames)
|
||||
|
||||
for _, paramName := range paramNames {
|
||||
paramValues := params[paramName]
|
||||
sort.Strings(paramValues)
|
||||
|
||||
// Join the sorted key values separated by ','
|
||||
// Then prepend "keyName:"; then add this string to the buffer
|
||||
cr.WriteString("\n" + paramName + ":" + strings.Join(paramValues, ","))
|
||||
}
|
||||
}
|
||||
return string(cr.Bytes()), nil
|
||||
}
|
137
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_credential_token.go
generated
vendored
Normal file
137
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_credential_token.go
generated
vendored
Normal file
|
@ -0,0 +1,137 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
)
|
||||
|
||||
// TokenRefresher represents a callback method that you write; this method is called periodically
|
||||
// so you can refresh the token credential's value.
|
||||
type TokenRefresher func(credential TokenCredential) time.Duration
|
||||
|
||||
// TokenCredential represents a token credential (which is also a pipeline.Factory).
|
||||
type TokenCredential interface {
|
||||
Credential
|
||||
Token() string
|
||||
SetToken(newToken string)
|
||||
}
|
||||
|
||||
// NewTokenCredential creates a token credential for use with role-based access control (RBAC) access to Azure Storage
|
||||
// resources. You initialize the TokenCredential with an initial token value. If you pass a non-nil value for
|
||||
// tokenRefresher, then the function you pass will be called immediately so it can refresh and change the
|
||||
// TokenCredential's token value by calling SetToken. Your tokenRefresher function must return a time.Duration
|
||||
// indicating how long the TokenCredential object should wait before calling your tokenRefresher function again.
|
||||
// If your tokenRefresher callback fails to refresh the token, you can return a duration of 0 to stop your
|
||||
// TokenCredential object from ever invoking tokenRefresher again. Also, oen way to deal with failing to refresh a
|
||||
// token is to cancel a context.Context object used by requests that have the TokenCredential object in their pipeline.
|
||||
func NewTokenCredential(initialToken string, tokenRefresher TokenRefresher) TokenCredential {
|
||||
tc := &tokenCredential{}
|
||||
tc.SetToken(initialToken) // We don't set it above to guarantee atomicity
|
||||
if tokenRefresher == nil {
|
||||
return tc // If no callback specified, return the simple tokenCredential
|
||||
}
|
||||
|
||||
tcwr := &tokenCredentialWithRefresh{token: tc}
|
||||
tcwr.token.startRefresh(tokenRefresher)
|
||||
runtime.SetFinalizer(tcwr, func(deadTC *tokenCredentialWithRefresh) {
|
||||
deadTC.token.stopRefresh()
|
||||
deadTC.token = nil // Sanity (not really required)
|
||||
})
|
||||
return tcwr
|
||||
}
|
||||
|
||||
// tokenCredentialWithRefresh is a wrapper over a token credential.
|
||||
// When this wrapper object gets GC'd, it stops the tokenCredential's timer
|
||||
// which allows the tokenCredential object to also be GC'd.
|
||||
type tokenCredentialWithRefresh struct {
|
||||
token *tokenCredential
|
||||
}
|
||||
|
||||
// credentialMarker is a package-internal method that exists just to satisfy the Credential interface.
|
||||
func (*tokenCredentialWithRefresh) credentialMarker() {}
|
||||
|
||||
// Token returns the current token value
|
||||
func (f *tokenCredentialWithRefresh) Token() string { return f.token.Token() }
|
||||
|
||||
// SetToken changes the current token value
|
||||
func (f *tokenCredentialWithRefresh) SetToken(token string) { f.token.SetToken(token) }
|
||||
|
||||
// New satisfies pipeline.Factory's New method creating a pipeline policy object.
|
||||
func (f *tokenCredentialWithRefresh) New(next pipeline.Policy, po *pipeline.PolicyOptions) pipeline.Policy {
|
||||
return f.token.New(next, po)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// tokenCredential is a pipeline.Factory is the credential's policy factory.
|
||||
type tokenCredential struct {
|
||||
token atomic.Value
|
||||
|
||||
// The members below are only used if the user specified a tokenRefresher callback function.
|
||||
timer *time.Timer
|
||||
tokenRefresher TokenRefresher
|
||||
lock sync.Mutex
|
||||
stopped bool
|
||||
}
|
||||
|
||||
// credentialMarker is a package-internal method that exists just to satisfy the Credential interface.
|
||||
func (*tokenCredential) credentialMarker() {}
|
||||
|
||||
// Token returns the current token value
|
||||
func (f *tokenCredential) Token() string { return f.token.Load().(string) }
|
||||
|
||||
// SetToken changes the current token value
|
||||
func (f *tokenCredential) SetToken(token string) { f.token.Store(token) }
|
||||
|
||||
// startRefresh calls refresh which immediately calls tokenRefresher
|
||||
// and then starts a timer to call tokenRefresher in the future.
|
||||
func (f *tokenCredential) startRefresh(tokenRefresher TokenRefresher) {
|
||||
f.tokenRefresher = tokenRefresher
|
||||
f.stopped = false // In case user calls StartRefresh, StopRefresh, & then StartRefresh again
|
||||
f.refresh()
|
||||
}
|
||||
|
||||
// refresh calls the user's tokenRefresher so they can refresh the token (by
|
||||
// calling SetToken) and then starts another time (based on the returned duration)
|
||||
// in order to refresh the token again in the future.
|
||||
func (f *tokenCredential) refresh() {
|
||||
d := f.tokenRefresher(f) // Invoke the user's refresh callback outside of the lock
|
||||
if d > 0 { // If duration is 0 or negative, refresher wants to not be called again
|
||||
f.lock.Lock()
|
||||
if !f.stopped {
|
||||
f.timer = time.AfterFunc(d, f.refresh)
|
||||
}
|
||||
f.lock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// stopRefresh stops any pending timer and sets stopped field to true to prevent
|
||||
// any new timer from starting.
|
||||
// NOTE: Stopping the timer allows the GC to destroy the tokenCredential object.
|
||||
func (f *tokenCredential) stopRefresh() {
|
||||
f.lock.Lock()
|
||||
f.stopped = true
|
||||
if f.timer != nil {
|
||||
f.timer.Stop()
|
||||
}
|
||||
f.lock.Unlock()
|
||||
}
|
||||
|
||||
// New satisfies pipeline.Factory's New method creating a pipeline policy object.
|
||||
func (f *tokenCredential) New(next pipeline.Policy, po *pipeline.PolicyOptions) pipeline.Policy {
|
||||
return pipeline.PolicyFunc(func(ctx context.Context, request pipeline.Request) (pipeline.Response, error) {
|
||||
if request.URL.Scheme != "https" {
|
||||
// HTTPS must be used, otherwise the tokens are at the risk of being exposed
|
||||
return nil, errors.New("token credentials require a URL using the https protocol scheme")
|
||||
}
|
||||
request.Header[headerAuthorization] = []string{"Bearer " + f.Token()}
|
||||
return next.Do(ctx, request)
|
||||
})
|
||||
}
|
28
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_mmf_unix.go
generated
vendored
Normal file
28
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_mmf_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
// +build linux darwin freebsd openbsd netbsd dragonfly solaris
|
||||
|
||||
package azblob
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type mmf []byte
|
||||
|
||||
func newMMF(file *os.File, writable bool, offset int64, length int) (mmf, error) {
|
||||
prot, flags := unix.PROT_READ, unix.MAP_SHARED // Assume read-only
|
||||
if writable {
|
||||
prot, flags = unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED
|
||||
}
|
||||
addr, err := unix.Mmap(int(file.Fd()), offset, length, prot, flags)
|
||||
return mmf(addr), err
|
||||
}
|
||||
|
||||
func (m *mmf) unmap() {
|
||||
err := unix.Munmap(*m)
|
||||
*m = nil
|
||||
if err != nil {
|
||||
panic("if we are unable to unmap the memory-mapped file, there is serious concern for memory corruption")
|
||||
}
|
||||
}
|
38
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_mmf_windows.go
generated
vendored
Normal file
38
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_mmf_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type mmf []byte
|
||||
|
||||
func newMMF(file *os.File, writable bool, offset int64, length int) (mmf, error) {
|
||||
prot, access := uint32(syscall.PAGE_READONLY), uint32(syscall.FILE_MAP_READ) // Assume read-only
|
||||
if writable {
|
||||
prot, access = uint32(syscall.PAGE_READWRITE), uint32(syscall.FILE_MAP_WRITE)
|
||||
}
|
||||
hMMF, errno := syscall.CreateFileMapping(syscall.Handle(file.Fd()), nil, prot, uint32(int64(length)>>32), uint32(int64(length)&0xffffffff), nil)
|
||||
if hMMF == 0 {
|
||||
return nil, os.NewSyscallError("CreateFileMapping", errno)
|
||||
}
|
||||
defer syscall.CloseHandle(hMMF)
|
||||
addr, errno := syscall.MapViewOfFile(hMMF, access, uint32(offset>>32), uint32(offset&0xffffffff), uintptr(length))
|
||||
m := mmf{}
|
||||
h := (*reflect.SliceHeader)(unsafe.Pointer(&m))
|
||||
h.Data = addr
|
||||
h.Len = length
|
||||
h.Cap = h.Len
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *mmf) unmap() {
|
||||
addr := uintptr(unsafe.Pointer(&(([]byte)(*m)[0])))
|
||||
*m = mmf{}
|
||||
err := syscall.UnmapViewOfFile(addr)
|
||||
if err != nil {
|
||||
panic("if we are unable to unmap the memory-mapped file, there is serious concern for memory corruption")
|
||||
}
|
||||
}
|
46
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_pipeline.go
generated
vendored
Normal file
46
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_pipeline.go
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
)
|
||||
|
||||
// PipelineOptions is used to configure a request policy pipeline's retry policy and logging.
|
||||
type PipelineOptions struct {
|
||||
// Log configures the pipeline's logging infrastructure indicating what information is logged and where.
|
||||
Log pipeline.LogOptions
|
||||
|
||||
// Retry configures the built-in retry policy behavior.
|
||||
Retry RetryOptions
|
||||
|
||||
// RequestLog configures the built-in request logging policy.
|
||||
RequestLog RequestLogOptions
|
||||
|
||||
// Telemetry configures the built-in telemetry policy behavior.
|
||||
Telemetry TelemetryOptions
|
||||
|
||||
// HTTPSender configures the sender of HTTP requests
|
||||
HTTPSender pipeline.Factory
|
||||
}
|
||||
|
||||
// NewPipeline creates a Pipeline using the specified credentials and options.
|
||||
func NewPipeline(c Credential, o PipelineOptions) pipeline.Pipeline {
|
||||
// Closest to API goes first; closest to the wire goes last
|
||||
f := []pipeline.Factory{
|
||||
NewTelemetryPolicyFactory(o.Telemetry),
|
||||
NewUniqueRequestIDPolicyFactory(),
|
||||
NewRetryPolicyFactory(o.Retry),
|
||||
}
|
||||
|
||||
if _, ok := c.(*anonymousCredentialPolicyFactory); !ok {
|
||||
// For AnonymousCredential, we optimize out the policy factory since it doesn't do anything
|
||||
// NOTE: The credential's policy factory must appear close to the wire so it can sign any
|
||||
// changes made by other factories (like UniqueRequestIDPolicyFactory)
|
||||
f = append(f, c)
|
||||
}
|
||||
f = append(f,
|
||||
NewRequestLogPolicyFactory(o.RequestLog),
|
||||
pipeline.MethodFactoryMarker()) // indicates at what stage in the pipeline the method factory is invoked
|
||||
|
||||
|
||||
return pipeline.NewPipeline(f, pipeline.Options{HTTPSender: o.HTTPSender, Log: o.Log})
|
||||
}
|
182
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_policy_request_log.go
generated
vendored
Normal file
182
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_policy_request_log.go
generated
vendored
Normal file
|
@ -0,0 +1,182 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
)
|
||||
|
||||
// RequestLogOptions configures the retry policy's behavior.
|
||||
type RequestLogOptions struct {
|
||||
// LogWarningIfTryOverThreshold logs a warning if a tried operation takes longer than the specified
|
||||
// duration (-1=no logging; 0=default threshold).
|
||||
LogWarningIfTryOverThreshold time.Duration
|
||||
}
|
||||
|
||||
func (o RequestLogOptions) defaults() RequestLogOptions {
|
||||
if o.LogWarningIfTryOverThreshold == 0 {
|
||||
// It would be good to relate this to https://azure.microsoft.com/en-us/support/legal/sla/storage/v1_2/
|
||||
// But this monitors the time to get the HTTP response; NOT the time to download the response body.
|
||||
o.LogWarningIfTryOverThreshold = 3 * time.Second // Default to 3 seconds
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// NewRequestLogPolicyFactory creates a RequestLogPolicyFactory object configured using the specified options.
|
||||
func NewRequestLogPolicyFactory(o RequestLogOptions) pipeline.Factory {
|
||||
o = o.defaults() // Force defaults to be calculated
|
||||
return pipeline.FactoryFunc(func(next pipeline.Policy, po *pipeline.PolicyOptions) pipeline.PolicyFunc {
|
||||
// These variables are per-policy; shared by multiple calls to Do
|
||||
var try int32
|
||||
operationStart := time.Now() // If this is the 1st try, record the operation state time
|
||||
return func(ctx context.Context, request pipeline.Request) (response pipeline.Response, err error) {
|
||||
try++ // The first try is #1 (not #0)
|
||||
|
||||
// Log the outgoing request as informational
|
||||
if po.ShouldLog(pipeline.LogInfo) {
|
||||
b := &bytes.Buffer{}
|
||||
fmt.Fprintf(b, "==> OUTGOING REQUEST (Try=%d)\n", try)
|
||||
pipeline.WriteRequestWithResponse(b, prepareRequestForLogging(request), nil, nil)
|
||||
po.Log(pipeline.LogInfo, b.String())
|
||||
}
|
||||
|
||||
// Set the time for this particular retry operation and then Do the operation.
|
||||
tryStart := time.Now()
|
||||
response, err = next.Do(ctx, request) // Make the request
|
||||
tryEnd := time.Now()
|
||||
tryDuration := tryEnd.Sub(tryStart)
|
||||
opDuration := tryEnd.Sub(operationStart)
|
||||
|
||||
logLevel, forceLog := pipeline.LogInfo, false // Default logging information
|
||||
|
||||
// If the response took too long, we'll upgrade to warning.
|
||||
if o.LogWarningIfTryOverThreshold > 0 && tryDuration > o.LogWarningIfTryOverThreshold {
|
||||
// Log a warning if the try duration exceeded the specified threshold
|
||||
logLevel, forceLog = pipeline.LogWarning, true
|
||||
}
|
||||
|
||||
if err == nil { // We got a response from the service
|
||||
sc := response.Response().StatusCode
|
||||
if ((sc >= 400 && sc <= 499) && sc != http.StatusNotFound && sc != http.StatusConflict && sc != http.StatusPreconditionFailed && sc != http.StatusRequestedRangeNotSatisfiable) || (sc >= 500 && sc <= 599) {
|
||||
logLevel, forceLog = pipeline.LogError, true // Promote to Error any 4xx (except those listed is an error) or any 5xx
|
||||
} else {
|
||||
// For other status codes, we leave the level as is.
|
||||
}
|
||||
} else { // This error did not get an HTTP response from the service; upgrade the severity to Error
|
||||
logLevel, forceLog = pipeline.LogError, true
|
||||
}
|
||||
|
||||
if shouldLog := po.ShouldLog(logLevel); forceLog || shouldLog {
|
||||
// We're going to log this; build the string to log
|
||||
b := &bytes.Buffer{}
|
||||
slow := ""
|
||||
if o.LogWarningIfTryOverThreshold > 0 && tryDuration > o.LogWarningIfTryOverThreshold {
|
||||
slow = fmt.Sprintf("[SLOW >%v]", o.LogWarningIfTryOverThreshold)
|
||||
}
|
||||
fmt.Fprintf(b, "==> REQUEST/RESPONSE (Try=%d/%v%s, OpTime=%v) -- ", try, tryDuration, slow, opDuration)
|
||||
if err != nil { // This HTTP request did not get a response from the service
|
||||
fmt.Fprint(b, "REQUEST ERROR\n")
|
||||
} else {
|
||||
if logLevel == pipeline.LogError {
|
||||
fmt.Fprint(b, "RESPONSE STATUS CODE ERROR\n")
|
||||
} else {
|
||||
fmt.Fprint(b, "RESPONSE SUCCESSFULLY RECEIVED\n")
|
||||
}
|
||||
}
|
||||
|
||||
pipeline.WriteRequestWithResponse(b, prepareRequestForLogging(request), response.Response(), err)
|
||||
if logLevel <= pipeline.LogError {
|
||||
b.Write(stack()) // For errors (or lower levels), we append the stack trace (an expensive operation)
|
||||
}
|
||||
msg := b.String()
|
||||
|
||||
if forceLog {
|
||||
pipeline.ForceLog(logLevel, msg)
|
||||
}
|
||||
if shouldLog {
|
||||
po.Log(logLevel, msg)
|
||||
}
|
||||
}
|
||||
return response, err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// RedactSigQueryParam redacts the 'sig' query parameter in URL's raw query to protect secret.
|
||||
func RedactSigQueryParam(rawQuery string) (bool, string) {
|
||||
rawQuery = strings.ToLower(rawQuery) // lowercase the string so we can look for ?sig= and &sig=
|
||||
sigFound := strings.Contains(rawQuery, "?sig=")
|
||||
if !sigFound {
|
||||
sigFound = strings.Contains(rawQuery, "&sig=")
|
||||
if !sigFound {
|
||||
return sigFound, rawQuery // [?|&]sig= not found; return same rawQuery passed in (no memory allocation)
|
||||
}
|
||||
}
|
||||
// [?|&]sig= found, redact its value
|
||||
values, _ := url.ParseQuery(rawQuery)
|
||||
for name := range values {
|
||||
if strings.EqualFold(name, "sig") {
|
||||
values[name] = []string{"REDACTED"}
|
||||
}
|
||||
}
|
||||
return sigFound, values.Encode()
|
||||
}
|
||||
|
||||
func prepareRequestForLogging(request pipeline.Request) *http.Request {
|
||||
req := request
|
||||
if sigFound, rawQuery := RedactSigQueryParam(req.URL.RawQuery); sigFound {
|
||||
// Make copy so we don't destroy the query parameters we actually need to send in the request
|
||||
req = request.Copy()
|
||||
req.Request.URL.RawQuery = rawQuery
|
||||
}
|
||||
|
||||
return prepareRequestForServiceLogging(req)
|
||||
}
|
||||
|
||||
func stack() []byte {
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n := runtime.Stack(buf, false)
|
||||
if n < len(buf) {
|
||||
return buf[:n]
|
||||
}
|
||||
buf = make([]byte, 2*len(buf))
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// Redact phase useful for blob and file service only. For other services,
|
||||
// this method can directly return request.Request.
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
func prepareRequestForServiceLogging(request pipeline.Request) *http.Request {
|
||||
req := request
|
||||
if exist, key := doesHeaderExistCaseInsensitive(req.Header, xMsCopySourceHeader); exist {
|
||||
req = request.Copy()
|
||||
url, err := url.Parse(req.Header.Get(key))
|
||||
if err == nil {
|
||||
if sigFound, rawQuery := RedactSigQueryParam(url.RawQuery); sigFound {
|
||||
url.RawQuery = rawQuery
|
||||
req.Header.Set(xMsCopySourceHeader, url.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
return req.Request
|
||||
}
|
||||
|
||||
const xMsCopySourceHeader = "x-ms-copy-source"
|
||||
|
||||
func doesHeaderExistCaseInsensitive(header http.Header, key string) (bool, string) {
|
||||
for keyInHeader := range header {
|
||||
if strings.EqualFold(keyInHeader, key) {
|
||||
return true, keyInHeader
|
||||
}
|
||||
}
|
||||
return false, ""
|
||||
}
|
414
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_policy_retry.go
generated
vendored
Normal file
414
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_policy_retry.go
generated
vendored
Normal file
|
@ -0,0 +1,414 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
)
|
||||
|
||||
// RetryPolicy tells the pipeline what kind of retry policy to use. See the RetryPolicy* constants.
|
||||
type RetryPolicy int32
|
||||
|
||||
const (
|
||||
// RetryPolicyExponential tells the pipeline to use an exponential back-off retry policy
|
||||
RetryPolicyExponential RetryPolicy = 0
|
||||
|
||||
// RetryPolicyFixed tells the pipeline to use a fixed back-off retry policy
|
||||
RetryPolicyFixed RetryPolicy = 1
|
||||
)
|
||||
|
||||
// RetryOptions configures the retry policy's behavior.
|
||||
type RetryOptions struct {
|
||||
// Policy tells the pipeline what kind of retry policy to use. See the RetryPolicy* constants.\
|
||||
// A value of zero means that you accept our default policy.
|
||||
Policy RetryPolicy
|
||||
|
||||
// MaxTries specifies the maximum number of attempts an operation will be tried before producing an error (0=default).
|
||||
// A value of zero means that you accept our default policy. A value of 1 means 1 try and no retries.
|
||||
MaxTries int32
|
||||
|
||||
// TryTimeout indicates the maximum time allowed for any single try of an HTTP request.
|
||||
// A value of zero means that you accept our default timeout. NOTE: When transferring large amounts
|
||||
// of data, the default TryTimeout will probably not be sufficient. You should override this value
|
||||
// based on the bandwidth available to the host machine and proximity to the Storage service. A good
|
||||
// starting point may be something like (60 seconds per MB of anticipated-payload-size).
|
||||
TryTimeout time.Duration
|
||||
|
||||
// RetryDelay specifies the amount of delay to use before retrying an operation (0=default).
|
||||
// When RetryPolicy is specified as RetryPolicyExponential, the delay increases exponentially
|
||||
// with each retry up to a maximum specified by MaxRetryDelay.
|
||||
// If you specify 0, then you must also specify 0 for MaxRetryDelay.
|
||||
// If you specify RetryDelay, then you must also specify MaxRetryDelay, and MaxRetryDelay should be
|
||||
// equal to or greater than RetryDelay.
|
||||
RetryDelay time.Duration
|
||||
|
||||
// MaxRetryDelay specifies the maximum delay allowed before retrying an operation (0=default).
|
||||
// If you specify 0, then you must also specify 0 for RetryDelay.
|
||||
MaxRetryDelay time.Duration
|
||||
|
||||
// RetryReadsFromSecondaryHost specifies whether the retry policy should retry a read operation against another host.
|
||||
// If RetryReadsFromSecondaryHost is "" (the default) then operations are not retried against another host.
|
||||
// NOTE: Before setting this field, make sure you understand the issues around reading stale & potentially-inconsistent
|
||||
// data at this webpage: https://docs.microsoft.com/en-us/azure/storage/common/storage-designing-ha-apps-with-ragrs
|
||||
RetryReadsFromSecondaryHost string // Comment this our for non-Blob SDKs
|
||||
}
|
||||
|
||||
func (o RetryOptions) retryReadsFromSecondaryHost() string {
|
||||
return o.RetryReadsFromSecondaryHost // This is for the Blob SDK only
|
||||
//return "" // This is for non-blob SDKs
|
||||
}
|
||||
|
||||
func (o RetryOptions) defaults() RetryOptions {
|
||||
// We assume the following:
|
||||
// 1. o.Policy should either be RetryPolicyExponential or RetryPolicyFixed
|
||||
// 2. o.MaxTries >= 0
|
||||
// 3. o.TryTimeout, o.RetryDelay, and o.MaxRetryDelay >=0
|
||||
// 4. o.RetryDelay <= o.MaxRetryDelay
|
||||
// 5. Both o.RetryDelay and o.MaxRetryDelay must be 0 or neither can be 0
|
||||
|
||||
IfDefault := func(current *time.Duration, desired time.Duration) {
|
||||
if *current == time.Duration(0) {
|
||||
*current = desired
|
||||
}
|
||||
}
|
||||
|
||||
// Set defaults if unspecified
|
||||
if o.MaxTries == 0 {
|
||||
o.MaxTries = 4
|
||||
}
|
||||
switch o.Policy {
|
||||
case RetryPolicyExponential:
|
||||
IfDefault(&o.TryTimeout, 1*time.Minute)
|
||||
IfDefault(&o.RetryDelay, 4*time.Second)
|
||||
IfDefault(&o.MaxRetryDelay, 120*time.Second)
|
||||
|
||||
case RetryPolicyFixed:
|
||||
IfDefault(&o.TryTimeout, 1*time.Minute)
|
||||
IfDefault(&o.RetryDelay, 30*time.Second)
|
||||
IfDefault(&o.MaxRetryDelay, 120*time.Second)
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func (o RetryOptions) calcDelay(try int32) time.Duration { // try is >=1; never 0
|
||||
pow := func(number int64, exponent int32) int64 { // pow is nested helper function
|
||||
var result int64 = 1
|
||||
for n := int32(0); n < exponent; n++ {
|
||||
result *= number
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
delay := time.Duration(0)
|
||||
switch o.Policy {
|
||||
case RetryPolicyExponential:
|
||||
delay = time.Duration(pow(2, try-1)-1) * o.RetryDelay
|
||||
|
||||
case RetryPolicyFixed:
|
||||
if try > 1 { // Any try after the 1st uses the fixed delay
|
||||
delay = o.RetryDelay
|
||||
}
|
||||
}
|
||||
|
||||
// Introduce some jitter: [0.0, 1.0) / 2 = [0.0, 0.5) + 0.8 = [0.8, 1.3)
|
||||
// For casts and rounding - be careful, as per https://github.com/golang/go/issues/20757
|
||||
delay = time.Duration(float32(delay) * (rand.Float32()/2 + 0.8)) // NOTE: We want math/rand; not crypto/rand
|
||||
if delay > o.MaxRetryDelay {
|
||||
delay = o.MaxRetryDelay
|
||||
}
|
||||
return delay
|
||||
}
|
||||
|
||||
// NewRetryPolicyFactory creates a RetryPolicyFactory object configured using the specified options.
|
||||
func NewRetryPolicyFactory(o RetryOptions) pipeline.Factory {
|
||||
o = o.defaults() // Force defaults to be calculated
|
||||
return pipeline.FactoryFunc(func(next pipeline.Policy, po *pipeline.PolicyOptions) pipeline.PolicyFunc {
|
||||
return func(ctx context.Context, request pipeline.Request) (response pipeline.Response, err error) {
|
||||
// Before each try, we'll select either the primary or secondary URL.
|
||||
primaryTry := int32(0) // This indicates how many tries we've attempted against the primary DC
|
||||
|
||||
// We only consider retrying against a secondary if we have a read request (GET/HEAD) AND this policy has a Secondary URL it can use
|
||||
considerSecondary := (request.Method == http.MethodGet || request.Method == http.MethodHead) && o.retryReadsFromSecondaryHost() != ""
|
||||
|
||||
// Exponential retry algorithm: ((2 ^ attempt) - 1) * delay * random(0.8, 1.2)
|
||||
// When to retry: connection failure or temporary/timeout. NOTE: StorageError considers HTTP 500/503 as temporary & is therefore retryable
|
||||
// If using a secondary:
|
||||
// Even tries go against primary; odd tries go against the secondary
|
||||
// For a primary wait ((2 ^ primaryTries - 1) * delay * random(0.8, 1.2)
|
||||
// If secondary gets a 404, don't fail, retry but future retries are only against the primary
|
||||
// When retrying against a secondary, ignore the retry count and wait (.1 second * random(0.8, 1.2))
|
||||
for try := int32(1); try <= o.MaxTries; try++ {
|
||||
logf("\n=====> Try=%d\n", try)
|
||||
|
||||
// Determine which endpoint to try. It's primary if there is no secondary or if it is an add # attempt.
|
||||
tryingPrimary := !considerSecondary || (try%2 == 1)
|
||||
// Select the correct host and delay
|
||||
if tryingPrimary {
|
||||
primaryTry++
|
||||
delay := o.calcDelay(primaryTry)
|
||||
logf("Primary try=%d, Delay=%v\n", primaryTry, delay)
|
||||
time.Sleep(delay) // The 1st try returns 0 delay
|
||||
} else {
|
||||
// For casts and rounding - be careful, as per https://github.com/golang/go/issues/20757
|
||||
delay := time.Duration(float32(time.Second) * (rand.Float32()/2 + 0.8))
|
||||
logf("Secondary try=%d, Delay=%v\n", try-primaryTry, delay)
|
||||
time.Sleep(delay) // Delay with some jitter before trying secondary
|
||||
}
|
||||
|
||||
// Clone the original request to ensure that each try starts with the original (unmutated) request.
|
||||
requestCopy := request.Copy()
|
||||
|
||||
// For each try, seek to the beginning of the Body stream. We do this even for the 1st try because
|
||||
// the stream may not be at offset 0 when we first get it and we want the same behavior for the
|
||||
// 1st try as for additional tries.
|
||||
err = requestCopy.RewindBody()
|
||||
if err != nil {
|
||||
return nil, errors.New("we must be able to seek on the Body Stream, otherwise retries would cause data corruption")
|
||||
}
|
||||
|
||||
if !tryingPrimary {
|
||||
requestCopy.URL.Host = o.retryReadsFromSecondaryHost()
|
||||
requestCopy.Host = o.retryReadsFromSecondaryHost()
|
||||
}
|
||||
|
||||
// Set the server-side timeout query parameter "timeout=[seconds]"
|
||||
timeout := int32(o.TryTimeout.Seconds()) // Max seconds per try
|
||||
if deadline, ok := ctx.Deadline(); ok { // If user's ctx has a deadline, make the timeout the smaller of the two
|
||||
t := int32(deadline.Sub(time.Now()).Seconds()) // Duration from now until user's ctx reaches its deadline
|
||||
logf("MaxTryTimeout=%d secs, TimeTilDeadline=%d sec\n", timeout, t)
|
||||
if t < timeout {
|
||||
timeout = t
|
||||
}
|
||||
if timeout < 0 {
|
||||
timeout = 0 // If timeout ever goes negative, set it to zero; this happen while debugging
|
||||
}
|
||||
logf("TryTimeout adjusted to=%d sec\n", timeout)
|
||||
}
|
||||
q := requestCopy.Request.URL.Query()
|
||||
q.Set("timeout", strconv.Itoa(int(timeout+1))) // Add 1 to "round up"
|
||||
requestCopy.Request.URL.RawQuery = q.Encode()
|
||||
logf("Url=%s\n", requestCopy.Request.URL.String())
|
||||
|
||||
// Set the time for this particular retry operation and then Do the operation.
|
||||
tryCtx, tryCancel := context.WithTimeout(ctx, time.Second*time.Duration(timeout))
|
||||
//requestCopy.Body = &deadlineExceededReadCloser{r: requestCopy.Request.Body}
|
||||
response, err = next.Do(tryCtx, requestCopy) // Make the request
|
||||
/*err = improveDeadlineExceeded(err)
|
||||
if err == nil {
|
||||
response.Response().Body = &deadlineExceededReadCloser{r: response.Response().Body}
|
||||
}*/
|
||||
logf("Err=%v, response=%v\n", err, response)
|
||||
|
||||
action := "" // This MUST get changed within the switch code below
|
||||
switch {
|
||||
case ctx.Err() != nil:
|
||||
action = "NoRetry: Op timeout"
|
||||
case !tryingPrimary && response != nil && response.Response() != nil && response.Response().StatusCode == http.StatusNotFound:
|
||||
// If attempt was against the secondary & it returned a StatusNotFound (404), then
|
||||
// the resource was not found. This may be due to replication delay. So, in this
|
||||
// case, we'll never try the secondary again for this operation.
|
||||
considerSecondary = false
|
||||
action = "Retry: Secondary URL returned 404"
|
||||
case err != nil:
|
||||
// NOTE: Protocol Responder returns non-nil if REST API returns invalid status code for the invoked operation.
|
||||
// Use ServiceCode to verify if the error is related to storage service-side,
|
||||
// ServiceCode is set only when error related to storage service happened.
|
||||
if stErr, ok := err.(StorageError); ok {
|
||||
if stErr.Temporary() {
|
||||
action = "Retry: StorageError with error service code and Temporary()"
|
||||
} else if stErr.Response() != nil && isSuccessStatusCode(stErr.Response()) { // TODO: This is a temporarily work around, remove this after protocol layer fix the issue that net.Error is wrapped as storageError
|
||||
action = "Retry: StorageError with success status code"
|
||||
} else {
|
||||
action = "NoRetry: StorageError not Temporary() and without retriable status code"
|
||||
}
|
||||
} else if netErr, ok := err.(net.Error); ok {
|
||||
// Use non-retriable net.Error list, but not retriable list.
|
||||
// As there are errors without Temporary() implementation,
|
||||
// while need be retried, like 'connection reset by peer', 'transport connection broken' and etc.
|
||||
// So the SDK do retry for most of the case, unless the error should not be retried for sure.
|
||||
if !isNotRetriable(netErr) {
|
||||
action = "Retry: net.Error and not in the non-retriable list"
|
||||
} else {
|
||||
action = "NoRetry: net.Error and in the non-retriable list"
|
||||
}
|
||||
} else if err == io.ErrUnexpectedEOF {
|
||||
action = "Retry: unexpected EOF"
|
||||
} else {
|
||||
action = "NoRetry: unrecognized error"
|
||||
}
|
||||
default:
|
||||
action = "NoRetry: successful HTTP request" // no error
|
||||
}
|
||||
|
||||
logf("Action=%s\n", action)
|
||||
// fmt.Println(action + "\n") // This is where we could log the retry operation; action is why we're retrying
|
||||
if action[0] != 'R' { // Retry only if action starts with 'R'
|
||||
if err != nil {
|
||||
tryCancel() // If we're returning an error, cancel this current/last per-retry timeout context
|
||||
} else {
|
||||
// We wrap the last per-try context in a body and overwrite the Response's Body field with our wrapper.
|
||||
// So, when the user closes the Body, the our per-try context gets closed too.
|
||||
// Another option, is that the Last Policy do this wrapping for a per-retry context (not for the user's context)
|
||||
if response == nil || response.Response() == nil {
|
||||
// We do panic in the case response or response.Response() is nil,
|
||||
// as for client, the response should not be nil if request is sent and the operations is executed successfully.
|
||||
// Another option, is that execute the cancel function when response or response.Response() is nil,
|
||||
// as in this case, current per-try has nothing to do in future.
|
||||
return nil, errors.New("invalid state, response should not be nil when the operation is executed successfully")
|
||||
}
|
||||
response.Response().Body = &contextCancelReadCloser{cf: tryCancel, body: response.Response().Body}
|
||||
}
|
||||
break // Don't retry
|
||||
}
|
||||
if response != nil && response.Response() != nil && response.Response().Body != nil {
|
||||
// If we're going to retry and we got a previous response, then flush its body to avoid leaking its TCP connection
|
||||
body := response.Response().Body
|
||||
io.Copy(ioutil.Discard, body)
|
||||
body.Close()
|
||||
}
|
||||
// If retrying, cancel the current per-try timeout context
|
||||
tryCancel()
|
||||
}
|
||||
return response, err // Not retryable or too many retries; return the last response/error
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// contextCancelReadCloser helps to invoke context's cancelFunc properly when the ReadCloser is closed.
|
||||
type contextCancelReadCloser struct {
|
||||
cf context.CancelFunc
|
||||
body io.ReadCloser
|
||||
}
|
||||
|
||||
func (rc *contextCancelReadCloser) Read(p []byte) (n int, err error) {
|
||||
return rc.body.Read(p)
|
||||
}
|
||||
|
||||
func (rc *contextCancelReadCloser) Close() error {
|
||||
err := rc.body.Close()
|
||||
if rc.cf != nil {
|
||||
rc.cf()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// isNotRetriable checks if the provided net.Error isn't retriable.
|
||||
func isNotRetriable(errToParse net.Error) bool {
|
||||
// No error, so this is NOT retriable.
|
||||
if errToParse == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// The error is either temporary or a timeout so it IS retriable (not not retriable).
|
||||
if errToParse.Temporary() || errToParse.Timeout() {
|
||||
return false
|
||||
}
|
||||
|
||||
genericErr := error(errToParse)
|
||||
|
||||
// From here all the error are neither Temporary() nor Timeout().
|
||||
switch err := errToParse.(type) {
|
||||
case *net.OpError:
|
||||
// The net.Error is also a net.OpError but the inner error is nil, so this is not retriable.
|
||||
if err.Err == nil {
|
||||
return true
|
||||
}
|
||||
genericErr = err.Err
|
||||
}
|
||||
|
||||
switch genericErr.(type) {
|
||||
case *net.AddrError, net.UnknownNetworkError, *net.DNSError, net.InvalidAddrError, *net.ParseError, *net.DNSConfigError:
|
||||
// If the error is one of the ones listed, then it is NOT retriable.
|
||||
return true
|
||||
}
|
||||
|
||||
// If it's invalid header field name/value error thrown by http module, then it is NOT retriable.
|
||||
// This could happen when metadata's key or value is invalid. (RoundTrip in transport.go)
|
||||
if strings.Contains(genericErr.Error(), "invalid header field") {
|
||||
return true
|
||||
}
|
||||
|
||||
// Assume the error is retriable.
|
||||
return false
|
||||
}
|
||||
|
||||
var successStatusCodes = []int{http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent, http.StatusPartialContent}
|
||||
|
||||
func isSuccessStatusCode(resp *http.Response) bool {
|
||||
if resp == nil {
|
||||
return false
|
||||
}
|
||||
for _, i := range successStatusCodes {
|
||||
if i == resp.StatusCode {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// According to https://github.com/golang/go/wiki/CompilerOptimizations, the compiler will inline this method and hopefully optimize all calls to it away
|
||||
var logf = func(format string, a ...interface{}) {}
|
||||
|
||||
// Use this version to see the retry method's code path (import "fmt")
|
||||
//var logf = fmt.Printf
|
||||
|
||||
/*
|
||||
type deadlineExceededReadCloser struct {
|
||||
r io.ReadCloser
|
||||
}
|
||||
|
||||
func (r *deadlineExceededReadCloser) Read(p []byte) (int, error) {
|
||||
n, err := 0, io.EOF
|
||||
if r.r != nil {
|
||||
n, err = r.r.Read(p)
|
||||
}
|
||||
return n, improveDeadlineExceeded(err)
|
||||
}
|
||||
func (r *deadlineExceededReadCloser) Seek(offset int64, whence int) (int64, error) {
|
||||
// For an HTTP request, the ReadCloser MUST also implement seek
|
||||
// For an HTTP response, Seek MUST not be called (or this will panic)
|
||||
o, err := r.r.(io.Seeker).Seek(offset, whence)
|
||||
return o, improveDeadlineExceeded(err)
|
||||
}
|
||||
func (r *deadlineExceededReadCloser) Close() error {
|
||||
if c, ok := r.r.(io.Closer); ok {
|
||||
c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// timeoutError is the internal struct that implements our richer timeout error.
|
||||
type deadlineExceeded struct {
|
||||
responseError
|
||||
}
|
||||
|
||||
var _ net.Error = (*deadlineExceeded)(nil) // Ensure deadlineExceeded implements the net.Error interface at compile time
|
||||
|
||||
// improveDeadlineExceeded creates a timeoutError object that implements the error interface IF cause is a context.DeadlineExceeded error.
|
||||
func improveDeadlineExceeded(cause error) error {
|
||||
// If cause is not DeadlineExceeded, return the same error passed in.
|
||||
if cause != context.DeadlineExceeded {
|
||||
return cause
|
||||
}
|
||||
// Else, convert DeadlineExceeded to our timeoutError which gives a richer string message
|
||||
return &deadlineExceeded{
|
||||
responseError: responseError{
|
||||
ErrorNode: pipeline.ErrorNode{}.Initialize(cause, 3),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Error implements the error interface's Error method to return a string representation of the error.
|
||||
func (e *deadlineExceeded) Error() string {
|
||||
return e.ErrorNode.Error("context deadline exceeded; when creating a pipeline, consider increasing RetryOptions' TryTimeout field")
|
||||
}
|
||||
*/
|
51
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_policy_telemetry.go
generated
vendored
Normal file
51
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_policy_telemetry.go
generated
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
)
|
||||
|
||||
// TelemetryOptions configures the telemetry policy's behavior.
|
||||
type TelemetryOptions struct {
|
||||
// Value is a string prepended to each request's User-Agent and sent to the service.
|
||||
// The service records the user-agent in logs for diagnostics and tracking of client requests.
|
||||
Value string
|
||||
}
|
||||
|
||||
// NewTelemetryPolicyFactory creates a factory that can create telemetry policy objects
|
||||
// which add telemetry information to outgoing HTTP requests.
|
||||
func NewTelemetryPolicyFactory(o TelemetryOptions) pipeline.Factory {
|
||||
b := &bytes.Buffer{}
|
||||
b.WriteString(o.Value)
|
||||
if b.Len() > 0 {
|
||||
b.WriteRune(' ')
|
||||
}
|
||||
fmt.Fprintf(b, "Azure-Storage/%s %s", serviceLibVersion, platformInfo)
|
||||
telemetryValue := b.String()
|
||||
|
||||
return pipeline.FactoryFunc(func(next pipeline.Policy, po *pipeline.PolicyOptions) pipeline.PolicyFunc {
|
||||
return func(ctx context.Context, request pipeline.Request) (pipeline.Response, error) {
|
||||
request.Header.Set("User-Agent", telemetryValue)
|
||||
return next.Do(ctx, request)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// NOTE: the ONLY function that should write to this variable is this func
|
||||
var platformInfo = func() string {
|
||||
// Azure-Storage/version (runtime; os type and version)”
|
||||
// Azure-Storage/1.4.0 (NODE-VERSION v4.5.0; Windows_NT 10.0.14393)'
|
||||
operatingSystem := runtime.GOOS // Default OS string
|
||||
switch operatingSystem {
|
||||
case "windows":
|
||||
operatingSystem = os.Getenv("OS") // Get more specific OS information
|
||||
case "linux": // accept default OS info
|
||||
case "freebsd": // accept default OS info
|
||||
}
|
||||
return fmt.Sprintf("(%s; %s)", runtime.Version(), operatingSystem)
|
||||
}()
|
24
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_policy_unique_request_id.go
generated
vendored
Normal file
24
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_policy_unique_request_id.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
)
|
||||
|
||||
// NewUniqueRequestIDPolicyFactory creates a UniqueRequestIDPolicyFactory object
|
||||
// that sets the request's x-ms-client-request-id header if it doesn't already exist.
|
||||
func NewUniqueRequestIDPolicyFactory() pipeline.Factory {
|
||||
return pipeline.FactoryFunc(func(next pipeline.Policy, po *pipeline.PolicyOptions) pipeline.PolicyFunc {
|
||||
// This is Policy's Do method:
|
||||
return func(ctx context.Context, request pipeline.Request) (pipeline.Response, error) {
|
||||
id := request.Header.Get(xMsClientRequestID)
|
||||
if id == "" { // Add a unique request ID if the caller didn't specify one already
|
||||
request.Header.Set(xMsClientRequestID, newUUID().String())
|
||||
}
|
||||
return next.Do(ctx, request)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const xMsClientRequestID = "x-ms-client-request-id"
|
184
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_retry_reader.go
generated
vendored
Normal file
184
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_retry_reader.go
generated
vendored
Normal file
|
@ -0,0 +1,184 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const CountToEnd = 0
|
||||
|
||||
// HTTPGetter is a function type that refers to a method that performs an HTTP GET operation.
|
||||
type HTTPGetter func(ctx context.Context, i HTTPGetterInfo) (*http.Response, error)
|
||||
|
||||
// HTTPGetterInfo is passed to an HTTPGetter function passing it parameters
|
||||
// that should be used to make an HTTP GET request.
|
||||
type HTTPGetterInfo struct {
|
||||
// Offset specifies the start offset that should be used when
|
||||
// creating the HTTP GET request's Range header
|
||||
Offset int64
|
||||
|
||||
// Count specifies the count of bytes that should be used to calculate
|
||||
// the end offset when creating the HTTP GET request's Range header
|
||||
Count int64
|
||||
|
||||
// ETag specifies the resource's etag that should be used when creating
|
||||
// the HTTP GET request's If-Match header
|
||||
ETag ETag
|
||||
}
|
||||
|
||||
// FailedReadNotifier is a function type that represents the notification function called when a read fails
|
||||
type FailedReadNotifier func(failureCount int, lastError error, offset int64, count int64, willRetry bool)
|
||||
|
||||
// RetryReaderOptions contains properties which can help to decide when to do retry.
|
||||
type RetryReaderOptions struct {
|
||||
// MaxRetryRequests specifies the maximum number of HTTP GET requests that will be made
|
||||
// while reading from a RetryReader. A value of zero means that no additional HTTP
|
||||
// GET requests will be made.
|
||||
MaxRetryRequests int
|
||||
doInjectError bool
|
||||
doInjectErrorRound int
|
||||
injectedError error
|
||||
|
||||
// NotifyFailedRead is called, if non-nil, after any failure to read. Expected usage is diagnostic logging.
|
||||
NotifyFailedRead FailedReadNotifier
|
||||
|
||||
// TreatEarlyCloseAsError can be set to true to prevent retries after "read on closed response body". By default,
|
||||
// retryReader has the following special behaviour: closing the response body before it is all read is treated as a
|
||||
// retryable error. This is to allow callers to force a retry by closing the body from another goroutine (e.g. if the =
|
||||
// read is too slow, caller may want to force a retry in the hope that the retry will be quicker). If
|
||||
// TreatEarlyCloseAsError is true, then retryReader's special behaviour is suppressed, and "read on closed body" is instead
|
||||
// treated as a fatal (non-retryable) error.
|
||||
// Note that setting TreatEarlyCloseAsError only guarantees that Closing will produce a fatal error if the Close happens
|
||||
// from the same "thread" (goroutine) as Read. Concurrent Close calls from other goroutines may instead produce network errors
|
||||
// which will be retried.
|
||||
TreatEarlyCloseAsError bool
|
||||
}
|
||||
|
||||
// retryReader implements io.ReaderCloser methods.
|
||||
// retryReader tries to read from response, and if there is retriable network error
|
||||
// returned during reading, it will retry according to retry reader option through executing
|
||||
// user defined action with provided data to get a new response, and continue the overall reading process
|
||||
// through reading from the new response.
|
||||
type retryReader struct {
|
||||
ctx context.Context
|
||||
info HTTPGetterInfo
|
||||
countWasBounded bool
|
||||
o RetryReaderOptions
|
||||
getter HTTPGetter
|
||||
|
||||
// we support Close-ing during Reads (from other goroutines), so we protect the shared state, which is response
|
||||
responseMu *sync.Mutex
|
||||
response *http.Response
|
||||
}
|
||||
|
||||
// NewRetryReader creates a retry reader.
|
||||
func NewRetryReader(ctx context.Context, initialResponse *http.Response,
|
||||
info HTTPGetterInfo, o RetryReaderOptions, getter HTTPGetter) io.ReadCloser {
|
||||
return &retryReader{
|
||||
ctx: ctx,
|
||||
getter: getter,
|
||||
info: info,
|
||||
countWasBounded: info.Count != CountToEnd,
|
||||
response: initialResponse,
|
||||
responseMu: &sync.Mutex{},
|
||||
o: o}
|
||||
}
|
||||
|
||||
func (s *retryReader) setResponse(r *http.Response) {
|
||||
s.responseMu.Lock()
|
||||
defer s.responseMu.Unlock()
|
||||
s.response = r
|
||||
}
|
||||
|
||||
func (s *retryReader) Read(p []byte) (n int, err error) {
|
||||
for try := 0; ; try++ {
|
||||
//fmt.Println(try) // Comment out for debugging.
|
||||
if s.countWasBounded && s.info.Count == CountToEnd {
|
||||
// User specified an original count and the remaining bytes are 0, return 0, EOF
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
s.responseMu.Lock()
|
||||
resp := s.response
|
||||
s.responseMu.Unlock()
|
||||
if resp == nil { // We don't have a response stream to read from, try to get one.
|
||||
newResponse, err := s.getter(s.ctx, s.info)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Successful GET; this is the network stream we'll read from.
|
||||
s.setResponse(newResponse)
|
||||
resp = newResponse
|
||||
}
|
||||
n, err := resp.Body.Read(p) // Read from the stream (this will return non-nil err if forceRetry is called, from another goroutine, while it is running)
|
||||
|
||||
// Injection mechanism for testing.
|
||||
if s.o.doInjectError && try == s.o.doInjectErrorRound {
|
||||
if s.o.injectedError != nil {
|
||||
err = s.o.injectedError
|
||||
} else {
|
||||
err = &net.DNSError{IsTemporary: true}
|
||||
}
|
||||
}
|
||||
|
||||
// We successfully read data or end EOF.
|
||||
if err == nil || err == io.EOF {
|
||||
s.info.Offset += int64(n) // Increments the start offset in case we need to make a new HTTP request in the future
|
||||
if s.info.Count != CountToEnd {
|
||||
s.info.Count -= int64(n) // Decrement the count in case we need to make a new HTTP request in the future
|
||||
}
|
||||
return n, err // Return the return to the caller
|
||||
}
|
||||
s.Close() // Error, close stream
|
||||
s.setResponse(nil) // Our stream is no longer good
|
||||
|
||||
// Check the retry count and error code, and decide whether to retry.
|
||||
retriesExhausted := try >= s.o.MaxRetryRequests
|
||||
_, isNetError := err.(net.Error)
|
||||
isUnexpectedEOF := err == io.ErrUnexpectedEOF
|
||||
willRetry := (isNetError || isUnexpectedEOF || s.wasRetryableEarlyClose(err)) && !retriesExhausted
|
||||
|
||||
// Notify, for logging purposes, of any failures
|
||||
if s.o.NotifyFailedRead != nil {
|
||||
failureCount := try + 1 // because try is zero-based
|
||||
s.o.NotifyFailedRead(failureCount, err, s.info.Offset, s.info.Count, willRetry)
|
||||
}
|
||||
|
||||
if willRetry {
|
||||
continue
|
||||
// Loop around and try to get and read from new stream.
|
||||
}
|
||||
return n, err // Not retryable, or retries exhausted, so just return
|
||||
}
|
||||
}
|
||||
|
||||
// By default, we allow early Closing, from another concurrent goroutine, to be used to force a retry
|
||||
// Is this safe, to close early from another goroutine? Early close ultimately ends up calling
|
||||
// net.Conn.Close, and that is documented as "Any blocked Read or Write operations will be unblocked and return errors"
|
||||
// which is exactly the behaviour we want.
|
||||
// NOTE: that if caller has forced an early Close from a separate goroutine (separate from the Read)
|
||||
// then there are two different types of error that may happen - either the one one we check for here,
|
||||
// or a net.Error (due to closure of connection). Which one happens depends on timing. We only need this routine
|
||||
// to check for one, since the other is a net.Error, which our main Read retry loop is already handing.
|
||||
func (s *retryReader) wasRetryableEarlyClose(err error) bool {
|
||||
if s.o.TreatEarlyCloseAsError {
|
||||
return false // user wants all early closes to be errors, and so not retryable
|
||||
}
|
||||
// unfortunately, http.errReadOnClosedResBody is private, so the best we can do here is to check for its text
|
||||
return strings.HasSuffix(err.Error(), ReadOnClosedBodyMessage)
|
||||
}
|
||||
|
||||
const ReadOnClosedBodyMessage = "read on closed response body"
|
||||
|
||||
func (s *retryReader) Close() error {
|
||||
s.responseMu.Lock()
|
||||
defer s.responseMu.Unlock()
|
||||
if s.response != nil && s.response.Body != nil {
|
||||
return s.response.Body.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
219
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_sas_account.go
generated
vendored
Normal file
219
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_sas_account.go
generated
vendored
Normal file
|
@ -0,0 +1,219 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AccountSASSignatureValues is used to generate a Shared Access Signature (SAS) for an Azure Storage account.
|
||||
// For more information, see https://docs.microsoft.com/rest/api/storageservices/constructing-an-account-sas
|
||||
type AccountSASSignatureValues struct {
|
||||
Version string `param:"sv"` // If not specified, this defaults to SASVersion
|
||||
Protocol SASProtocol `param:"spr"` // See the SASProtocol* constants
|
||||
StartTime time.Time `param:"st"` // Not specified if IsZero
|
||||
ExpiryTime time.Time `param:"se"` // Not specified if IsZero
|
||||
Permissions string `param:"sp"` // Create by initializing a AccountSASPermissions and then call String()
|
||||
IPRange IPRange `param:"sip"`
|
||||
Services string `param:"ss"` // Create by initializing AccountSASServices and then call String()
|
||||
ResourceTypes string `param:"srt"` // Create by initializing AccountSASResourceTypes and then call String()
|
||||
}
|
||||
|
||||
// NewSASQueryParameters uses an account's shared key credential to sign this signature values to produce
|
||||
// the proper SAS query parameters.
|
||||
func (v AccountSASSignatureValues) NewSASQueryParameters(sharedKeyCredential *SharedKeyCredential) (SASQueryParameters, error) {
|
||||
// https://docs.microsoft.com/en-us/rest/api/storageservices/Constructing-an-Account-SAS
|
||||
if v.ExpiryTime.IsZero() || v.Permissions == "" || v.ResourceTypes == "" || v.Services == "" {
|
||||
return SASQueryParameters{}, errors.New("account SAS is missing at least one of these: ExpiryTime, Permissions, Service, or ResourceType")
|
||||
}
|
||||
if v.Version == "" {
|
||||
v.Version = SASVersion
|
||||
}
|
||||
perms := &AccountSASPermissions{}
|
||||
if err := perms.Parse(v.Permissions); err != nil {
|
||||
return SASQueryParameters{}, err
|
||||
}
|
||||
v.Permissions = perms.String()
|
||||
|
||||
startTime, expiryTime, _ := FormatTimesForSASSigning(v.StartTime, v.ExpiryTime, time.Time{})
|
||||
|
||||
stringToSign := strings.Join([]string{
|
||||
sharedKeyCredential.AccountName(),
|
||||
v.Permissions,
|
||||
v.Services,
|
||||
v.ResourceTypes,
|
||||
startTime,
|
||||
expiryTime,
|
||||
v.IPRange.String(),
|
||||
string(v.Protocol),
|
||||
v.Version,
|
||||
""}, // That right, the account SAS requires a terminating extra newline
|
||||
"\n")
|
||||
|
||||
signature := sharedKeyCredential.ComputeHMACSHA256(stringToSign)
|
||||
p := SASQueryParameters{
|
||||
// Common SAS parameters
|
||||
version: v.Version,
|
||||
protocol: v.Protocol,
|
||||
startTime: v.StartTime,
|
||||
expiryTime: v.ExpiryTime,
|
||||
permissions: v.Permissions,
|
||||
ipRange: v.IPRange,
|
||||
|
||||
// Account-specific SAS parameters
|
||||
services: v.Services,
|
||||
resourceTypes: v.ResourceTypes,
|
||||
|
||||
// Calculated SAS signature
|
||||
signature: signature,
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// The AccountSASPermissions type simplifies creating the permissions string for an Azure Storage Account SAS.
|
||||
// Initialize an instance of this type and then call its String method to set AccountSASSignatureValues's Permissions field.
|
||||
type AccountSASPermissions struct {
|
||||
Read, Write, Delete, List, Add, Create, Update, Process bool
|
||||
}
|
||||
|
||||
// String produces the SAS permissions string for an Azure Storage account.
|
||||
// Call this method to set AccountSASSignatureValues's Permissions field.
|
||||
func (p AccountSASPermissions) String() string {
|
||||
var buffer bytes.Buffer
|
||||
if p.Read {
|
||||
buffer.WriteRune('r')
|
||||
}
|
||||
if p.Write {
|
||||
buffer.WriteRune('w')
|
||||
}
|
||||
if p.Delete {
|
||||
buffer.WriteRune('d')
|
||||
}
|
||||
if p.List {
|
||||
buffer.WriteRune('l')
|
||||
}
|
||||
if p.Add {
|
||||
buffer.WriteRune('a')
|
||||
}
|
||||
if p.Create {
|
||||
buffer.WriteRune('c')
|
||||
}
|
||||
if p.Update {
|
||||
buffer.WriteRune('u')
|
||||
}
|
||||
if p.Process {
|
||||
buffer.WriteRune('p')
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// Parse initializes the AccountSASPermissions's fields from a string.
|
||||
func (p *AccountSASPermissions) Parse(s string) error {
|
||||
*p = AccountSASPermissions{} // Clear out the flags
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case 'r':
|
||||
p.Read = true
|
||||
case 'w':
|
||||
p.Write = true
|
||||
case 'd':
|
||||
p.Delete = true
|
||||
case 'l':
|
||||
p.List = true
|
||||
case 'a':
|
||||
p.Add = true
|
||||
case 'c':
|
||||
p.Create = true
|
||||
case 'u':
|
||||
p.Update = true
|
||||
case 'p':
|
||||
p.Process = true
|
||||
default:
|
||||
return fmt.Errorf("Invalid permission character: '%v'", r)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// The AccountSASServices type simplifies creating the services string for an Azure Storage Account SAS.
|
||||
// Initialize an instance of this type and then call its String method to set AccountSASSignatureValues's Services field.
|
||||
type AccountSASServices struct {
|
||||
Blob, Queue, File bool
|
||||
}
|
||||
|
||||
// String produces the SAS services string for an Azure Storage account.
|
||||
// Call this method to set AccountSASSignatureValues's Services field.
|
||||
func (s AccountSASServices) String() string {
|
||||
var buffer bytes.Buffer
|
||||
if s.Blob {
|
||||
buffer.WriteRune('b')
|
||||
}
|
||||
if s.Queue {
|
||||
buffer.WriteRune('q')
|
||||
}
|
||||
if s.File {
|
||||
buffer.WriteRune('f')
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// Parse initializes the AccountSASServices' fields from a string.
|
||||
func (a *AccountSASServices) Parse(s string) error {
|
||||
*a = AccountSASServices{} // Clear out the flags
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case 'b':
|
||||
a.Blob = true
|
||||
case 'q':
|
||||
a.Queue = true
|
||||
case 'f':
|
||||
a.File = true
|
||||
default:
|
||||
return fmt.Errorf("Invalid service character: '%v'", r)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// The AccountSASResourceTypes type simplifies creating the resource types string for an Azure Storage Account SAS.
|
||||
// Initialize an instance of this type and then call its String method to set AccountSASSignatureValues's ResourceTypes field.
|
||||
type AccountSASResourceTypes struct {
|
||||
Service, Container, Object bool
|
||||
}
|
||||
|
||||
// String produces the SAS resource types string for an Azure Storage account.
|
||||
// Call this method to set AccountSASSignatureValues's ResourceTypes field.
|
||||
func (rt AccountSASResourceTypes) String() string {
|
||||
var buffer bytes.Buffer
|
||||
if rt.Service {
|
||||
buffer.WriteRune('s')
|
||||
}
|
||||
if rt.Container {
|
||||
buffer.WriteRune('c')
|
||||
}
|
||||
if rt.Object {
|
||||
buffer.WriteRune('o')
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// Parse initializes the AccountSASResourceType's fields from a string.
|
||||
func (rt *AccountSASResourceTypes) Parse(s string) error {
|
||||
*rt = AccountSASResourceTypes{} // Clear out the flags
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case 's':
|
||||
rt.Service = true
|
||||
case 'c':
|
||||
rt.Container = true
|
||||
case 'o':
|
||||
rt.Object = true
|
||||
default:
|
||||
return fmt.Errorf("Invalid resource type: '%v'", r)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
358
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_sas_query_params.go
generated
vendored
Normal file
358
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_sas_query_params.go
generated
vendored
Normal file
|
@ -0,0 +1,358 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SASVersion indicates the SAS version.
|
||||
const SASVersion = ServiceVersion
|
||||
|
||||
type SASProtocol string
|
||||
|
||||
const (
|
||||
// SASProtocolHTTPS can be specified for a SAS protocol
|
||||
SASProtocolHTTPS SASProtocol = "https"
|
||||
|
||||
// SASProtocolHTTPSandHTTP can be specified for a SAS protocol
|
||||
SASProtocolHTTPSandHTTP SASProtocol = "https,http"
|
||||
)
|
||||
|
||||
// FormatTimesForSASSigning converts a time.Time to a snapshotTimeFormat string suitable for a
|
||||
// SASField's StartTime or ExpiryTime fields. Returns "" if value.IsZero().
|
||||
func FormatTimesForSASSigning(startTime, expiryTime, snapshotTime time.Time) (string, string, string) {
|
||||
ss := ""
|
||||
if !startTime.IsZero() {
|
||||
ss = formatSASTimeWithDefaultFormat(&startTime)
|
||||
}
|
||||
se := ""
|
||||
if !expiryTime.IsZero() {
|
||||
se = formatSASTimeWithDefaultFormat(&expiryTime)
|
||||
}
|
||||
sh := ""
|
||||
if !snapshotTime.IsZero() {
|
||||
sh = snapshotTime.Format(SnapshotTimeFormat)
|
||||
}
|
||||
return ss, se, sh
|
||||
}
|
||||
|
||||
// SASTimeFormat represents the format of a SAS start or expiry time. Use it when formatting/parsing a time.Time.
|
||||
const SASTimeFormat = "2006-01-02T15:04:05Z" //"2017-07-27T00:00:00Z" // ISO 8601
|
||||
var SASTimeFormats = []string{"2006-01-02T15:04:05.0000000Z", SASTimeFormat, "2006-01-02T15:04Z", "2006-01-02"} // ISO 8601 formats, please refer to https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas for more details.
|
||||
|
||||
// formatSASTimeWithDefaultFormat format time with ISO 8601 in "yyyy-MM-ddTHH:mm:ssZ".
|
||||
func formatSASTimeWithDefaultFormat(t *time.Time) string {
|
||||
return formatSASTime(t, SASTimeFormat) // By default, "yyyy-MM-ddTHH:mm:ssZ" is used
|
||||
}
|
||||
|
||||
// formatSASTime format time with given format, use ISO 8601 in "yyyy-MM-ddTHH:mm:ssZ" by default.
|
||||
func formatSASTime(t *time.Time, format string) string {
|
||||
if format != "" {
|
||||
return t.Format(format)
|
||||
}
|
||||
return t.Format(SASTimeFormat) // By default, "yyyy-MM-ddTHH:mm:ssZ" is used
|
||||
}
|
||||
|
||||
// parseSASTimeString try to parse sas time string.
|
||||
func parseSASTimeString(val string) (t time.Time, timeFormat string, err error) {
|
||||
for _, sasTimeFormat := range SASTimeFormats {
|
||||
t, err = time.Parse(sasTimeFormat, val)
|
||||
if err == nil {
|
||||
timeFormat = sasTimeFormat
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = errors.New("fail to parse time with IOS 8601 formats, please refer to https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas for more details")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas
|
||||
|
||||
// A SASQueryParameters object represents the components that make up an Azure Storage SAS' query parameters.
|
||||
// You parse a map of query parameters into its fields by calling NewSASQueryParameters(). You add the components
|
||||
// to a query parameter map by calling AddToValues().
|
||||
// NOTE: Changing any field requires computing a new SAS signature using a XxxSASSignatureValues type.
|
||||
//
|
||||
// This type defines the components used by all Azure Storage resources (Containers, Blobs, Files, & Queues).
|
||||
type SASQueryParameters struct {
|
||||
// All members are immutable or values so copies of this struct are goroutine-safe.
|
||||
version string `param:"sv"`
|
||||
services string `param:"ss"`
|
||||
resourceTypes string `param:"srt"`
|
||||
protocol SASProtocol `param:"spr"`
|
||||
startTime time.Time `param:"st"`
|
||||
expiryTime time.Time `param:"se"`
|
||||
snapshotTime time.Time `param:"snapshot"`
|
||||
ipRange IPRange `param:"sip"`
|
||||
identifier string `param:"si"`
|
||||
resource string `param:"sr"`
|
||||
permissions string `param:"sp"`
|
||||
signature string `param:"sig"`
|
||||
cacheControl string `param:"rscc"`
|
||||
contentDisposition string `param:"rscd"`
|
||||
contentEncoding string `param:"rsce"`
|
||||
contentLanguage string `param:"rscl"`
|
||||
contentType string `param:"rsct"`
|
||||
signedOid string `param:"skoid"`
|
||||
signedTid string `param:"sktid"`
|
||||
signedStart time.Time `param:"skt"`
|
||||
signedExpiry time.Time `param:"ske"`
|
||||
signedService string `param:"sks"`
|
||||
signedVersion string `param:"skv"`
|
||||
|
||||
// private member used for startTime and expiryTime formatting.
|
||||
stTimeFormat string
|
||||
seTimeFormat string
|
||||
}
|
||||
|
||||
func (p *SASQueryParameters) SignedOid() string {
|
||||
return p.signedOid
|
||||
}
|
||||
|
||||
func (p *SASQueryParameters) SignedTid() string {
|
||||
return p.signedTid
|
||||
}
|
||||
|
||||
func (p *SASQueryParameters) SignedStart() time.Time {
|
||||
return p.signedStart
|
||||
}
|
||||
|
||||
func (p *SASQueryParameters) SignedExpiry() time.Time {
|
||||
return p.signedExpiry
|
||||
}
|
||||
|
||||
func (p *SASQueryParameters) SignedService() string {
|
||||
return p.signedService
|
||||
}
|
||||
|
||||
func (p *SASQueryParameters) SignedVersion() string {
|
||||
return p.signedVersion
|
||||
}
|
||||
|
||||
func (p *SASQueryParameters) SnapshotTime() time.Time {
|
||||
return p.snapshotTime
|
||||
}
|
||||
|
||||
func (p *SASQueryParameters) Version() string {
|
||||
return p.version
|
||||
}
|
||||
|
||||
func (p *SASQueryParameters) Services() string {
|
||||
return p.services
|
||||
}
|
||||
func (p *SASQueryParameters) ResourceTypes() string {
|
||||
return p.resourceTypes
|
||||
}
|
||||
func (p *SASQueryParameters) Protocol() SASProtocol {
|
||||
return p.protocol
|
||||
}
|
||||
func (p *SASQueryParameters) StartTime() time.Time {
|
||||
return p.startTime
|
||||
}
|
||||
func (p *SASQueryParameters) ExpiryTime() time.Time {
|
||||
return p.expiryTime
|
||||
}
|
||||
|
||||
func (p *SASQueryParameters) IPRange() IPRange {
|
||||
return p.ipRange
|
||||
}
|
||||
|
||||
func (p *SASQueryParameters) Identifier() string {
|
||||
return p.identifier
|
||||
}
|
||||
|
||||
func (p *SASQueryParameters) Resource() string {
|
||||
return p.resource
|
||||
}
|
||||
func (p *SASQueryParameters) Permissions() string {
|
||||
return p.permissions
|
||||
}
|
||||
|
||||
func (p *SASQueryParameters) Signature() string {
|
||||
return p.signature
|
||||
}
|
||||
|
||||
func (p *SASQueryParameters) CacheControl() string {
|
||||
return p.cacheControl
|
||||
}
|
||||
|
||||
func (p *SASQueryParameters) ContentDisposition() string {
|
||||
return p.contentDisposition
|
||||
}
|
||||
|
||||
func (p *SASQueryParameters) ContentEncoding() string {
|
||||
return p.contentEncoding
|
||||
}
|
||||
|
||||
func (p *SASQueryParameters) ContentLanguage() string {
|
||||
return p.contentLanguage
|
||||
}
|
||||
|
||||
func (p *SASQueryParameters) ContentType() string {
|
||||
return p.contentType
|
||||
}
|
||||
|
||||
// IPRange represents a SAS IP range's start IP and (optionally) end IP.
|
||||
type IPRange struct {
|
||||
Start net.IP // Not specified if length = 0
|
||||
End net.IP // Not specified if length = 0
|
||||
}
|
||||
|
||||
// String returns a string representation of an IPRange.
|
||||
func (ipr *IPRange) String() string {
|
||||
if len(ipr.Start) == 0 {
|
||||
return ""
|
||||
}
|
||||
start := ipr.Start.String()
|
||||
if len(ipr.End) == 0 {
|
||||
return start
|
||||
}
|
||||
return start + "-" + ipr.End.String()
|
||||
}
|
||||
|
||||
// NewSASQueryParameters creates and initializes a SASQueryParameters object based on the
|
||||
// query parameter map's passed-in values. If deleteSASParametersFromValues is true,
|
||||
// all SAS-related query parameters are removed from the passed-in map. If
|
||||
// deleteSASParametersFromValues is false, the map passed-in map is unaltered.
|
||||
func newSASQueryParameters(values url.Values, deleteSASParametersFromValues bool) SASQueryParameters {
|
||||
p := SASQueryParameters{}
|
||||
for k, v := range values {
|
||||
val := v[0]
|
||||
isSASKey := true
|
||||
switch strings.ToLower(k) {
|
||||
case "sv":
|
||||
p.version = val
|
||||
case "ss":
|
||||
p.services = val
|
||||
case "srt":
|
||||
p.resourceTypes = val
|
||||
case "spr":
|
||||
p.protocol = SASProtocol(val)
|
||||
case "snapshot":
|
||||
p.snapshotTime, _ = time.Parse(SnapshotTimeFormat, val)
|
||||
case "st":
|
||||
p.startTime, p.stTimeFormat, _ = parseSASTimeString(val)
|
||||
case "se":
|
||||
p.expiryTime, p.seTimeFormat, _ = parseSASTimeString(val)
|
||||
case "sip":
|
||||
dashIndex := strings.Index(val, "-")
|
||||
if dashIndex == -1 {
|
||||
p.ipRange.Start = net.ParseIP(val)
|
||||
} else {
|
||||
p.ipRange.Start = net.ParseIP(val[:dashIndex])
|
||||
p.ipRange.End = net.ParseIP(val[dashIndex+1:])
|
||||
}
|
||||
case "si":
|
||||
p.identifier = val
|
||||
case "sr":
|
||||
p.resource = val
|
||||
case "sp":
|
||||
p.permissions = val
|
||||
case "sig":
|
||||
p.signature = val
|
||||
case "rscc":
|
||||
p.cacheControl = val
|
||||
case "rscd":
|
||||
p.contentDisposition = val
|
||||
case "rsce":
|
||||
p.contentEncoding = val
|
||||
case "rscl":
|
||||
p.contentLanguage = val
|
||||
case "rsct":
|
||||
p.contentType = val
|
||||
case "skoid":
|
||||
p.signedOid = val
|
||||
case "sktid":
|
||||
p.signedTid = val
|
||||
case "skt":
|
||||
p.signedStart, _ = time.Parse(SASTimeFormat, val)
|
||||
case "ske":
|
||||
p.signedExpiry, _ = time.Parse(SASTimeFormat, val)
|
||||
case "sks":
|
||||
p.signedService = val
|
||||
case "skv":
|
||||
p.signedVersion = val
|
||||
default:
|
||||
isSASKey = false // We didn't recognize the query parameter
|
||||
}
|
||||
if isSASKey && deleteSASParametersFromValues {
|
||||
delete(values, k)
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// AddToValues adds the SAS components to the specified query parameters map.
|
||||
func (p *SASQueryParameters) addToValues(v url.Values) url.Values {
|
||||
if p.version != "" {
|
||||
v.Add("sv", p.version)
|
||||
}
|
||||
if p.services != "" {
|
||||
v.Add("ss", p.services)
|
||||
}
|
||||
if p.resourceTypes != "" {
|
||||
v.Add("srt", p.resourceTypes)
|
||||
}
|
||||
if p.protocol != "" {
|
||||
v.Add("spr", string(p.protocol))
|
||||
}
|
||||
if !p.startTime.IsZero() {
|
||||
v.Add("st", formatSASTime(&(p.startTime), p.stTimeFormat))
|
||||
}
|
||||
if !p.expiryTime.IsZero() {
|
||||
v.Add("se", formatSASTime(&(p.expiryTime), p.seTimeFormat))
|
||||
}
|
||||
if len(p.ipRange.Start) > 0 {
|
||||
v.Add("sip", p.ipRange.String())
|
||||
}
|
||||
if p.identifier != "" {
|
||||
v.Add("si", p.identifier)
|
||||
}
|
||||
if p.resource != "" {
|
||||
v.Add("sr", p.resource)
|
||||
}
|
||||
if p.permissions != "" {
|
||||
v.Add("sp", p.permissions)
|
||||
}
|
||||
if p.signedOid != "" {
|
||||
v.Add("skoid", p.signedOid)
|
||||
v.Add("sktid", p.signedTid)
|
||||
v.Add("skt", p.signedStart.Format(SASTimeFormat))
|
||||
v.Add("ske", p.signedExpiry.Format(SASTimeFormat))
|
||||
v.Add("sks", p.signedService)
|
||||
v.Add("skv", p.signedVersion)
|
||||
}
|
||||
if p.signature != "" {
|
||||
v.Add("sig", p.signature)
|
||||
}
|
||||
if p.cacheControl != "" {
|
||||
v.Add("rscc", p.cacheControl)
|
||||
}
|
||||
if p.contentDisposition != "" {
|
||||
v.Add("rscd", p.contentDisposition)
|
||||
}
|
||||
if p.contentEncoding != "" {
|
||||
v.Add("rsce", p.contentEncoding)
|
||||
}
|
||||
if p.contentLanguage != "" {
|
||||
v.Add("rscl", p.contentLanguage)
|
||||
}
|
||||
if p.contentType != "" {
|
||||
v.Add("rsct", p.contentType)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Encode encodes the SAS query parameters into URL encoded form sorted by key.
|
||||
func (p *SASQueryParameters) Encode() string {
|
||||
v := url.Values{}
|
||||
p.addToValues(v)
|
||||
return v.Encode()
|
||||
}
|
131
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_service_codes_common.go
generated
vendored
Normal file
131
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_service_codes_common.go
generated
vendored
Normal file
|
@ -0,0 +1,131 @@
|
|||
package azblob
|
||||
|
||||
// https://docs.microsoft.com/en-us/rest/api/storageservices/common-rest-api-error-codes
|
||||
|
||||
const (
|
||||
// ServiceCodeNone is the default value. It indicates that the error was related to the service or that the service didn't return a code.
|
||||
ServiceCodeNone ServiceCodeType = ""
|
||||
|
||||
// ServiceCodeAccountAlreadyExists means the specified account already exists.
|
||||
ServiceCodeAccountAlreadyExists ServiceCodeType = "AccountAlreadyExists"
|
||||
|
||||
// ServiceCodeAccountBeingCreated means the specified account is in the process of being created (403).
|
||||
ServiceCodeAccountBeingCreated ServiceCodeType = "AccountBeingCreated"
|
||||
|
||||
// ServiceCodeAccountIsDisabled means the specified account is disabled (403).
|
||||
ServiceCodeAccountIsDisabled ServiceCodeType = "AccountIsDisabled"
|
||||
|
||||
// ServiceCodeAuthenticationFailed means the server failed to authenticate the request. Make sure the value of the Authorization header is formed correctly including the signature (403).
|
||||
ServiceCodeAuthenticationFailed ServiceCodeType = "AuthenticationFailed"
|
||||
|
||||
// ServiceCodeConditionHeadersNotSupported means the condition headers are not supported (400).
|
||||
ServiceCodeConditionHeadersNotSupported ServiceCodeType = "ConditionHeadersNotSupported"
|
||||
|
||||
// ServiceCodeConditionNotMet means the condition specified in the conditional header(s) was not met for a read/write operation (304/412).
|
||||
ServiceCodeConditionNotMet ServiceCodeType = "ConditionNotMet"
|
||||
|
||||
// ServiceCodeEmptyMetadataKey means the key for one of the metadata key-value pairs is empty (400).
|
||||
ServiceCodeEmptyMetadataKey ServiceCodeType = "EmptyMetadataKey"
|
||||
|
||||
// ServiceCodeInsufficientAccountPermissions means read operations are currently disabled or Write operations are not allowed or The account being accessed does not have sufficient permissions to execute this operation (403).
|
||||
ServiceCodeInsufficientAccountPermissions ServiceCodeType = "InsufficientAccountPermissions"
|
||||
|
||||
// ServiceCodeInternalError means the server encountered an internal error. Please retry the request (500).
|
||||
ServiceCodeInternalError ServiceCodeType = "InternalError"
|
||||
|
||||
// ServiceCodeInvalidAuthenticationInfo means the authentication information was not provided in the correct format. Verify the value of Authorization header (400).
|
||||
ServiceCodeInvalidAuthenticationInfo ServiceCodeType = "InvalidAuthenticationInfo"
|
||||
|
||||
// ServiceCodeInvalidHeaderValue means the value provided for one of the HTTP headers was not in the correct format (400).
|
||||
ServiceCodeInvalidHeaderValue ServiceCodeType = "InvalidHeaderValue"
|
||||
|
||||
// ServiceCodeInvalidHTTPVerb means the HTTP verb specified was not recognized by the server (400).
|
||||
ServiceCodeInvalidHTTPVerb ServiceCodeType = "InvalidHttpVerb"
|
||||
|
||||
// ServiceCodeInvalidInput means one of the request inputs is not valid (400).
|
||||
ServiceCodeInvalidInput ServiceCodeType = "InvalidInput"
|
||||
|
||||
// ServiceCodeInvalidMd5 means the MD5 value specified in the request is invalid. The MD5 value must be 128 bits and Base64-encoded (400).
|
||||
ServiceCodeInvalidMd5 ServiceCodeType = "InvalidMd5"
|
||||
|
||||
// ServiceCodeInvalidMetadata means the specified metadata is invalid. It includes characters that are not permitted (400).
|
||||
ServiceCodeInvalidMetadata ServiceCodeType = "InvalidMetadata"
|
||||
|
||||
// ServiceCodeInvalidQueryParameterValue means an invalid value was specified for one of the query parameters in the request URI (400).
|
||||
ServiceCodeInvalidQueryParameterValue ServiceCodeType = "InvalidQueryParameterValue"
|
||||
|
||||
// ServiceCodeInvalidRange means the range specified is invalid for the current size of the resource (416).
|
||||
ServiceCodeInvalidRange ServiceCodeType = "InvalidRange"
|
||||
|
||||
// ServiceCodeInvalidResourceName means the specified resource name contains invalid characters (400).
|
||||
ServiceCodeInvalidResourceName ServiceCodeType = "InvalidResourceName"
|
||||
|
||||
// ServiceCodeInvalidURI means the requested URI does not represent any resource on the server (400).
|
||||
ServiceCodeInvalidURI ServiceCodeType = "InvalidUri"
|
||||
|
||||
// ServiceCodeInvalidXMLDocument means the specified XML is not syntactically valid (400).
|
||||
ServiceCodeInvalidXMLDocument ServiceCodeType = "InvalidXmlDocument"
|
||||
|
||||
// ServiceCodeInvalidXMLNodeValue means the value provided for one of the XML nodes in the request body was not in the correct format (400).
|
||||
ServiceCodeInvalidXMLNodeValue ServiceCodeType = "InvalidXmlNodeValue"
|
||||
|
||||
// ServiceCodeMd5Mismatch means the MD5 value specified in the request did not match the MD5 value calculated by the server (400).
|
||||
ServiceCodeMd5Mismatch ServiceCodeType = "Md5Mismatch"
|
||||
|
||||
// ServiceCodeMetadataTooLarge means the size of the specified metadata exceeds the maximum size permitted (400).
|
||||
ServiceCodeMetadataTooLarge ServiceCodeType = "MetadataTooLarge"
|
||||
|
||||
// ServiceCodeMissingContentLengthHeader means the Content-Length header was not specified (411).
|
||||
ServiceCodeMissingContentLengthHeader ServiceCodeType = "MissingContentLengthHeader"
|
||||
|
||||
// ServiceCodeMissingRequiredQueryParameter means a required query parameter was not specified for this request (400).
|
||||
ServiceCodeMissingRequiredQueryParameter ServiceCodeType = "MissingRequiredQueryParameter"
|
||||
|
||||
// ServiceCodeMissingRequiredHeader means a required HTTP header was not specified (400).
|
||||
ServiceCodeMissingRequiredHeader ServiceCodeType = "MissingRequiredHeader"
|
||||
|
||||
// ServiceCodeMissingRequiredXMLNode means a required XML node was not specified in the request body (400).
|
||||
ServiceCodeMissingRequiredXMLNode ServiceCodeType = "MissingRequiredXmlNode"
|
||||
|
||||
// ServiceCodeMultipleConditionHeadersNotSupported means multiple condition headers are not supported (400).
|
||||
ServiceCodeMultipleConditionHeadersNotSupported ServiceCodeType = "MultipleConditionHeadersNotSupported"
|
||||
|
||||
// ServiceCodeOperationTimedOut means the operation could not be completed within the permitted time (500).
|
||||
ServiceCodeOperationTimedOut ServiceCodeType = "OperationTimedOut"
|
||||
|
||||
// ServiceCodeOutOfRangeInput means one of the request inputs is out of range (400).
|
||||
ServiceCodeOutOfRangeInput ServiceCodeType = "OutOfRangeInput"
|
||||
|
||||
// ServiceCodeOutOfRangeQueryParameterValue means a query parameter specified in the request URI is outside the permissible range (400).
|
||||
ServiceCodeOutOfRangeQueryParameterValue ServiceCodeType = "OutOfRangeQueryParameterValue"
|
||||
|
||||
// ServiceCodeRequestBodyTooLarge means the size of the request body exceeds the maximum size permitted (413).
|
||||
ServiceCodeRequestBodyTooLarge ServiceCodeType = "RequestBodyTooLarge"
|
||||
|
||||
// ServiceCodeResourceTypeMismatch means the specified resource type does not match the type of the existing resource (409).
|
||||
ServiceCodeResourceTypeMismatch ServiceCodeType = "ResourceTypeMismatch"
|
||||
|
||||
// ServiceCodeRequestURLFailedToParse means the url in the request could not be parsed (400).
|
||||
ServiceCodeRequestURLFailedToParse ServiceCodeType = "RequestUrlFailedToParse"
|
||||
|
||||
// ServiceCodeResourceAlreadyExists means the specified resource already exists (409).
|
||||
ServiceCodeResourceAlreadyExists ServiceCodeType = "ResourceAlreadyExists"
|
||||
|
||||
// ServiceCodeResourceNotFound means the specified resource does not exist (404).
|
||||
ServiceCodeResourceNotFound ServiceCodeType = "ResourceNotFound"
|
||||
|
||||
// ServiceCodeServerBusy means the server is currently unable to receive requests. Please retry your request or Ingress/egress is over the account limit or operations per second is over the account limit (503).
|
||||
ServiceCodeServerBusy ServiceCodeType = "ServerBusy"
|
||||
|
||||
// ServiceCodeUnsupportedHeader means one of the HTTP headers specified in the request is not supported (400).
|
||||
ServiceCodeUnsupportedHeader ServiceCodeType = "UnsupportedHeader"
|
||||
|
||||
// ServiceCodeUnsupportedXMLNode means one of the XML nodes specified in the request body is not supported (400).
|
||||
ServiceCodeUnsupportedXMLNode ServiceCodeType = "UnsupportedXmlNode"
|
||||
|
||||
// ServiceCodeUnsupportedQueryParameter means one of the query parameters specified in the request URI is not supported (400).
|
||||
ServiceCodeUnsupportedQueryParameter ServiceCodeType = "UnsupportedQueryParameter"
|
||||
|
||||
// ServiceCodeUnsupportedHTTPVerb means the resource doesn't support the specified HTTP verb (405).
|
||||
ServiceCodeUnsupportedHTTPVerb ServiceCodeType = "UnsupportedHttpVerb"
|
||||
)
|
111
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_storage_error.go
generated
vendored
Normal file
111
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_storage_error.go
generated
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// wire up our custom error handling constructor
|
||||
responseErrorFactory = newStorageError
|
||||
}
|
||||
|
||||
// ServiceCodeType is a string identifying a storage service error.
|
||||
// For more information, see https://docs.microsoft.com/en-us/rest/api/storageservices/status-and-error-codes2
|
||||
type ServiceCodeType string
|
||||
|
||||
// StorageError identifies a responder-generated network or response parsing error.
|
||||
type StorageError interface {
|
||||
// ResponseError implements error's Error(), net.Error's Temporary() and Timeout() methods & Response().
|
||||
ResponseError
|
||||
|
||||
// ServiceCode returns a service error code. Your code can use this to make error recovery decisions.
|
||||
ServiceCode() ServiceCodeType
|
||||
}
|
||||
|
||||
// storageError is the internal struct that implements the public StorageError interface.
|
||||
type storageError struct {
|
||||
responseError
|
||||
serviceCode ServiceCodeType
|
||||
details map[string]string
|
||||
}
|
||||
|
||||
// newStorageError creates an error object that implements the error interface.
|
||||
func newStorageError(cause error, response *http.Response, description string) error {
|
||||
return &storageError{
|
||||
responseError: responseError{
|
||||
ErrorNode: pipeline.ErrorNode{}.Initialize(cause, 3),
|
||||
response: response,
|
||||
description: description,
|
||||
},
|
||||
serviceCode: ServiceCodeType(response.Header.Get("x-ms-error-code")),
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceCode returns service-error information. The caller may examine these values but should not modify any of them.
|
||||
func (e *storageError) ServiceCode() ServiceCodeType {
|
||||
return e.serviceCode
|
||||
}
|
||||
|
||||
// Error implements the error interface's Error method to return a string representation of the error.
|
||||
func (e *storageError) Error() string {
|
||||
b := &bytes.Buffer{}
|
||||
fmt.Fprintf(b, "===== RESPONSE ERROR (ServiceCode=%s) =====\n", e.serviceCode)
|
||||
fmt.Fprintf(b, "Description=%s, Details: ", e.description)
|
||||
if len(e.details) == 0 {
|
||||
b.WriteString("(none)\n")
|
||||
} else {
|
||||
b.WriteRune('\n')
|
||||
keys := make([]string, 0, len(e.details))
|
||||
// Alphabetize the details
|
||||
for k := range e.details {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
fmt.Fprintf(b, " %s: %+v\n", k, e.details[k])
|
||||
}
|
||||
}
|
||||
req := pipeline.Request{Request: e.response.Request}.Copy() // Make a copy of the response's request
|
||||
pipeline.WriteRequestWithResponse(b, prepareRequestForLogging(req), e.response, nil)
|
||||
return e.ErrorNode.Error(b.String())
|
||||
}
|
||||
|
||||
// Temporary returns true if the error occurred due to a temporary condition (including an HTTP status of 500 or 503).
|
||||
func (e *storageError) Temporary() bool {
|
||||
if e.response != nil {
|
||||
if (e.response.StatusCode == http.StatusInternalServerError) || (e.response.StatusCode == http.StatusServiceUnavailable) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return e.ErrorNode.Temporary()
|
||||
}
|
||||
|
||||
// UnmarshalXML performs custom unmarshalling of XML-formatted Azure storage request errors.
|
||||
func (e *storageError) UnmarshalXML(d *xml.Decoder, start xml.StartElement) (err error) {
|
||||
tokName := ""
|
||||
var t xml.Token
|
||||
for t, err = d.Token(); err == nil; t, err = d.Token() {
|
||||
switch tt := t.(type) {
|
||||
case xml.StartElement:
|
||||
tokName = tt.Name.Local
|
||||
break
|
||||
case xml.CharData:
|
||||
switch tokName {
|
||||
case "Message":
|
||||
e.description = string(tt)
|
||||
default:
|
||||
if e.details == nil {
|
||||
e.details = map[string]string{}
|
||||
}
|
||||
e.details[tokName] = string(tt)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
64
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_util_validate.go
generated
vendored
Normal file
64
vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_util_validate.go
generated
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// httpRange defines a range of bytes within an HTTP resource, starting at offset and
|
||||
// ending at offset+count. A zero-value httpRange indicates the entire resource. An httpRange
|
||||
// which has an offset but na zero value count indicates from the offset to the resource's end.
|
||||
type httpRange struct {
|
||||
offset int64
|
||||
count int64
|
||||
}
|
||||
|
||||
func (r httpRange) pointers() *string {
|
||||
if r.offset == 0 && r.count == CountToEnd { // Do common case first for performance
|
||||
return nil // No specified range
|
||||
}
|
||||
endOffset := "" // if count == CountToEnd (0)
|
||||
if r.count > 0 {
|
||||
endOffset = strconv.FormatInt((r.offset+r.count)-1, 10)
|
||||
}
|
||||
dataRange := fmt.Sprintf("bytes=%v-%s", r.offset, endOffset)
|
||||
return &dataRange
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func validateSeekableStreamAt0AndGetCount(body io.ReadSeeker) (int64, error) {
|
||||
if body == nil { // nil body's are "logically" seekable to 0 and are 0 bytes long
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
err := validateSeekableStreamAt0(body)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
count, err := body.Seek(0, io.SeekEnd)
|
||||
if err != nil {
|
||||
return 0, errors.New("body stream must be seekable")
|
||||
}
|
||||
|
||||
body.Seek(0, io.SeekStart)
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// return an error if body is not a valid seekable stream at 0
|
||||
func validateSeekableStreamAt0(body io.ReadSeeker) error {
|
||||
if body == nil { // nil body's are "logically" seekable to 0
|
||||
return nil
|
||||
}
|
||||
if pos, err := body.Seek(0, io.SeekCurrent); pos != 0 || err != nil {
|
||||
// Help detect programmer error
|
||||
if err != nil {
|
||||
return errors.New("body stream must be seekable")
|
||||
}
|
||||
return errors.New("body stream must be set to position 0")
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// The UUID reserved variants.
|
||||
const (
|
||||
reservedNCS byte = 0x80
|
||||
reservedRFC4122 byte = 0x40
|
||||
reservedMicrosoft byte = 0x20
|
||||
reservedFuture byte = 0x00
|
||||
)
|
||||
|
||||
// A UUID representation compliant with specification in RFC 4122 document.
|
||||
type uuid [16]byte
|
||||
|
||||
// NewUUID returns a new uuid using RFC 4122 algorithm.
|
||||
func newUUID() (u uuid) {
|
||||
u = uuid{}
|
||||
// Set all bits to randomly (or pseudo-randomly) chosen values.
|
||||
rand.Read(u[:])
|
||||
u[8] = (u[8] | reservedRFC4122) & 0x7F // u.setVariant(ReservedRFC4122)
|
||||
|
||||
var version byte = 4
|
||||
u[6] = (u[6] & 0xF) | (version << 4) // u.setVersion(4)
|
||||
return
|
||||
}
|
||||
|
||||
// String returns an unparsed version of the generated UUID sequence.
|
||||
func (u uuid) String() string {
|
||||
return fmt.Sprintf("%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:])
|
||||
}
|
||||
|
||||
// ParseUUID parses a string formatted as "003020100-0504-0706-0809-0a0b0c0d0e0f"
|
||||
// or "{03020100-0504-0706-0809-0a0b0c0d0e0f}" into a UUID.
|
||||
func parseUUID(uuidStr string) uuid {
|
||||
char := func(hexString string) byte {
|
||||
i, _ := strconv.ParseUint(hexString, 16, 8)
|
||||
return byte(i)
|
||||
}
|
||||
if uuidStr[0] == '{' {
|
||||
uuidStr = uuidStr[1:] // Skip over the '{'
|
||||
}
|
||||
// 03020100 - 05 04 - 07 06 - 08 09 - 0a 0b 0c 0d 0e 0f
|
||||
// 1 11 1 11 11 1 12 22 2 22 22 22 33 33 33
|
||||
// 01234567 8 90 12 3 45 67 8 90 12 3 45 67 89 01 23 45
|
||||
uuidVal := uuid{
|
||||
char(uuidStr[0:2]),
|
||||
char(uuidStr[2:4]),
|
||||
char(uuidStr[4:6]),
|
||||
char(uuidStr[6:8]),
|
||||
|
||||
char(uuidStr[9:11]),
|
||||
char(uuidStr[11:13]),
|
||||
|
||||
char(uuidStr[14:16]),
|
||||
char(uuidStr[16:18]),
|
||||
|
||||
char(uuidStr[19:21]),
|
||||
char(uuidStr[21:23]),
|
||||
|
||||
char(uuidStr[24:26]),
|
||||
char(uuidStr[26:28]),
|
||||
char(uuidStr[28:30]),
|
||||
char(uuidStr[30:32]),
|
||||
char(uuidStr[32:34]),
|
||||
char(uuidStr[34:36]),
|
||||
}
|
||||
return uuidVal
|
||||
}
|
||||
|
||||
func (u uuid) bytes() []byte {
|
||||
return u[:]
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright 2017 Microsoft Corporation. All rights reserved.
|
||||
// Use of this source code is governed by an MIT
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package azblob allows you to manipulate Azure Storage containers and blobs objects.
|
||||
|
||||
URL Types
|
||||
|
||||
The most common types you'll work with are the XxxURL types. The methods of these types make requests
|
||||
against the Azure Storage Service.
|
||||
|
||||
- ServiceURL's methods perform operations on a storage account.
|
||||
- ContainerURL's methods perform operations on an account's container.
|
||||
- BlockBlobURL's methods perform operations on a container's block blob.
|
||||
- AppendBlobURL's methods perform operations on a container's append blob.
|
||||
- PageBlobURL's methods perform operations on a container's page blob.
|
||||
- BlobURL's methods perform operations on a container's blob regardless of the blob's type.
|
||||
|
||||
Internally, each XxxURL object contains a URL and a request pipeline. The URL indicates the endpoint where each HTTP
|
||||
request is sent and the pipeline indicates how the outgoing HTTP request and incoming HTTP response is processed.
|
||||
The pipeline specifies things like retry policies, logging, deserialization of HTTP response payloads, and more.
|
||||
|
||||
Pipelines are threadsafe and may be shared by multiple XxxURL objects. When you create a ServiceURL, you pass
|
||||
an initial pipeline. When you call ServiceURL's NewContainerURL method, the new ContainerURL object has its own
|
||||
URL but it shares the same pipeline as the parent ServiceURL object.
|
||||
|
||||
To work with a blob, call one of ContainerURL's 4 NewXxxBlobURL methods depending on how you want to treat the blob.
|
||||
To treat the blob as a block blob, append blob, or page blob, call NewBlockBlobURL, NewAppendBlobURL, or NewPageBlobURL
|
||||
respectively. These three types are all identical except for the methods they expose; each type exposes the methods
|
||||
relevant to the type of blob represented. If you're not sure how you want to treat a blob, you can call NewBlobURL;
|
||||
this returns an object whose methods are relevant to any kind of blob. When you call ContainerURL's NewXxxBlobURL,
|
||||
the new XxxBlobURL object has its own URL but it shares the same pipeline as the parent ContainerURL object. You
|
||||
can easily switch between blob types (method sets) by calling a ToXxxBlobURL method.
|
||||
|
||||
If you'd like to use a different pipeline with a ServiceURL, ContainerURL, or XxxBlobURL object, then call the XxxURL
|
||||
object's WithPipeline method passing in the desired pipeline. The WithPipeline methods create a new XxxURL object
|
||||
with the same URL as the original but with the specified pipeline.
|
||||
|
||||
Note that XxxURL objects use little memory, are goroutine-safe, and many objects share the same pipeline. This means that
|
||||
XxxURL objects share a lot of system resources making them very efficient.
|
||||
|
||||
All of XxxURL's methods that make HTTP requests return rich error handling information so you can discern network failures,
|
||||
transient failures, timeout failures, service failures, etc. See the StorageError interface for more information and an
|
||||
example of how to do deal with errors.
|
||||
|
||||
URL and Shared Access Signature Manipulation
|
||||
|
||||
The library includes a BlobURLParts type for deconstructing and reconstructing URLs. And you can use the following types
|
||||
for generating and parsing Shared Access Signature (SAS)
|
||||
- Use the AccountSASSignatureValues type to create a SAS for a storage account.
|
||||
- Use the BlobSASSignatureValues type to create a SAS for a container or blob.
|
||||
- Use the SASQueryParameters type to turn signature values in to query parameres or to parse query parameters.
|
||||
|
||||
To generate a SAS, you must use the SharedKeyCredential type.
|
||||
|
||||
Credentials
|
||||
|
||||
When creating a request pipeline, you must specify one of this package's credential types.
|
||||
- Call the NewAnonymousCredential function for requests that contain a Shared Access Signature (SAS).
|
||||
- Call the NewSharedKeyCredential function (with an account name & key) to access any account resources. You must also use this
|
||||
to generate Shared Access Signatures.
|
||||
|
||||
HTTP Request Policy Factories
|
||||
|
||||
This package defines several request policy factories for use with the pipeline package.
|
||||
Most applications will not use these factories directly; instead, the NewPipeline
|
||||
function creates these factories, initializes them (via the PipelineOptions type)
|
||||
and returns a pipeline object for use by the XxxURL objects.
|
||||
|
||||
However, for advanced scenarios, developers can access these policy factories directly
|
||||
and even create their own and then construct their own pipeline in order to affect HTTP
|
||||
requests and responses performed by the XxxURL objects. For example, developers can
|
||||
introduce their own logging, random failures, request recording & playback for fast
|
||||
testing, HTTP request pacing, alternate retry mechanisms, metering, metrics, etc. The
|
||||
possibilities are endless!
|
||||
|
||||
Below are the request pipeline policy factory functions that are provided with this
|
||||
package:
|
||||
- NewRetryPolicyFactory Enables rich retry semantics for failed HTTP requests.
|
||||
- NewRequestLogPolicyFactory Enables rich logging support for HTTP requests/responses & failures.
|
||||
- NewTelemetryPolicyFactory Enables simple modification of the HTTP request's User-Agent header so each request reports the SDK version & language/runtime making the requests.
|
||||
- NewUniqueRequestIDPolicyFactory Adds a x-ms-client-request-id header with a unique UUID value to an HTTP request to help with diagnosing failures.
|
||||
|
||||
Also, note that all the NewXxxCredential functions return request policy factory objects which get injected into the pipeline.
|
||||
*/
|
||||
package azblob
|
||||
|
||||
// TokenCredential Use this to access resources using Role-Based Access Control (RBAC).
|
403
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_append_blob.go
generated
vendored
Normal file
403
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_append_blob.go
generated
vendored
Normal file
|
@ -0,0 +1,403 @@
|
|||
package azblob
|
||||
|
||||
// Code generated by Microsoft (R) AutoRest Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// appendBlobClient is the client for the AppendBlob methods of the Azblob service.
|
||||
type appendBlobClient struct {
|
||||
managementClient
|
||||
}
|
||||
|
||||
// newAppendBlobClient creates an instance of the appendBlobClient client.
|
||||
func newAppendBlobClient(url url.URL, p pipeline.Pipeline) appendBlobClient {
|
||||
return appendBlobClient{newManagementClient(url, p)}
|
||||
}
|
||||
|
||||
// AppendBlock the Append Block operation commits a new block of data to the end of an existing append blob. The Append
|
||||
// Block operation is permitted only if the blob was created with x-ms-blob-type set to AppendBlob. Append Block is
|
||||
// supported only on version 2015-02-21 version or later.
|
||||
//
|
||||
// body is initial data body will be closed upon successful return. Callers should ensure closure when receiving an
|
||||
// error.contentLength is the length of the request. timeout is the timeout parameter is expressed in seconds. For more
|
||||
// information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> transactionalContentMD5 is specify the transactional md5 for the body, to
|
||||
// be validated by the service. transactionalContentCrc64 is specify the transactional crc64 for the body, to be
|
||||
// validated by the service. leaseID is if specified, the operation only succeeds if the resource's lease is active and
|
||||
// matches this ID. maxSize is optional conditional header. The max length in bytes permitted for the append blob. If
|
||||
// the Append Block operation would cause the blob to exceed that limit or if the blob size is already greater than the
|
||||
// value specified in this header, the request will fail with MaxBlobSizeConditionNotMet error (HTTP status code 412 -
|
||||
// Precondition Failed). appendPosition is optional conditional header, used only for the Append Block operation. A
|
||||
// number indicating the byte offset to compare. Append Block will succeed only if the append position is equal to this
|
||||
// number. If it is not, the request will fail with the AppendPositionConditionNotMet error (HTTP status code 412 -
|
||||
// Precondition Failed). encryptionKey is optional. Specifies the encryption key to use to encrypt the data provided in
|
||||
// the request. If not specified, encryption is performed with the root account encryption key. For more information,
|
||||
// see Encryption at Rest for Azure Storage Services. encryptionKeySha256 is the SHA-256 hash of the provided
|
||||
// encryption key. Must be provided if the x-ms-encryption-key header is provided. encryptionAlgorithm is the algorithm
|
||||
// used to produce the encryption key hash. Currently, the only accepted value is "AES256". Must be provided if the
|
||||
// x-ms-encryption-key header is provided. ifModifiedSince is specify this header value to operate only on a blob if it
|
||||
// has been modified since the specified date/time. ifUnmodifiedSince is specify this header value to operate only on a
|
||||
// blob if it has not been modified since the specified date/time. ifMatch is specify an ETag value to operate only on
|
||||
// blobs with a matching value. ifNoneMatch is specify an ETag value to operate only on blobs without a matching value.
|
||||
// requestID is provides a client-generated, opaque value with a 1 KB character limit that is recorded in the analytics
|
||||
// logs when storage analytics logging is enabled.
|
||||
func (client appendBlobClient) AppendBlock(ctx context.Context, body io.ReadSeeker, contentLength int64, timeout *int32, transactionalContentMD5 []byte, transactionalContentCrc64 []byte, leaseID *string, maxSize *int64, appendPosition *int64, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (*AppendBlobAppendBlockResponse, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: body,
|
||||
constraints: []constraint{{target: "body", name: null, rule: true, chain: nil}}},
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.appendBlockPreparer(body, contentLength, timeout, transactionalContentMD5, transactionalContentCrc64, leaseID, maxSize, appendPosition, encryptionKey, encryptionKeySha256, encryptionAlgorithm, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.appendBlockResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*AppendBlobAppendBlockResponse), err
|
||||
}
|
||||
|
||||
// appendBlockPreparer prepares the AppendBlock request.
|
||||
func (client appendBlobClient) appendBlockPreparer(body io.ReadSeeker, contentLength int64, timeout *int32, transactionalContentMD5 []byte, transactionalContentCrc64 []byte, leaseID *string, maxSize *int64, appendPosition *int64, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("PUT", client.url, body)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
params.Set("comp", "appendblock")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
req.Header.Set("Content-Length", strconv.FormatInt(contentLength, 10))
|
||||
if transactionalContentMD5 != nil {
|
||||
req.Header.Set("Content-MD5", base64.StdEncoding.EncodeToString(transactionalContentMD5))
|
||||
}
|
||||
if transactionalContentCrc64 != nil {
|
||||
req.Header.Set("x-ms-content-crc64", base64.StdEncoding.EncodeToString(transactionalContentCrc64))
|
||||
}
|
||||
if leaseID != nil {
|
||||
req.Header.Set("x-ms-lease-id", *leaseID)
|
||||
}
|
||||
if maxSize != nil {
|
||||
req.Header.Set("x-ms-blob-condition-maxsize", strconv.FormatInt(*maxSize, 10))
|
||||
}
|
||||
if appendPosition != nil {
|
||||
req.Header.Set("x-ms-blob-condition-appendpos", strconv.FormatInt(*appendPosition, 10))
|
||||
}
|
||||
if encryptionKey != nil {
|
||||
req.Header.Set("x-ms-encryption-key", *encryptionKey)
|
||||
}
|
||||
if encryptionKeySha256 != nil {
|
||||
req.Header.Set("x-ms-encryption-key-sha256", *encryptionKeySha256)
|
||||
}
|
||||
if encryptionAlgorithm != EncryptionAlgorithmNone {
|
||||
req.Header.Set("x-ms-encryption-algorithm", string(encryptionAlgorithm))
|
||||
}
|
||||
if ifModifiedSince != nil {
|
||||
req.Header.Set("If-Modified-Since", (*ifModifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifUnmodifiedSince != nil {
|
||||
req.Header.Set("If-Unmodified-Since", (*ifUnmodifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifMatch != nil {
|
||||
req.Header.Set("If-Match", string(*ifMatch))
|
||||
}
|
||||
if ifNoneMatch != nil {
|
||||
req.Header.Set("If-None-Match", string(*ifNoneMatch))
|
||||
}
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// appendBlockResponder handles the response to the AppendBlock request.
|
||||
func (client appendBlobClient) appendBlockResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK, http.StatusCreated)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(ioutil.Discard, resp.Response().Body)
|
||||
resp.Response().Body.Close()
|
||||
return &AppendBlobAppendBlockResponse{rawResponse: resp.Response()}, err
|
||||
}
|
||||
|
||||
// AppendBlockFromURL the Append Block operation commits a new block of data to the end of an existing append blob
|
||||
// where the contents are read from a source url. The Append Block operation is permitted only if the blob was created
|
||||
// with x-ms-blob-type set to AppendBlob. Append Block is supported only on version 2015-02-21 version or later.
|
||||
//
|
||||
// sourceURL is specify a URL to the copy source. contentLength is the length of the request. sourceRange is bytes of
|
||||
// source data in the specified range. sourceContentMD5 is specify the md5 calculated for the range of bytes that must
|
||||
// be read from the copy source. sourceContentcrc64 is specify the crc64 calculated for the range of bytes that must be
|
||||
// read from the copy source. timeout is the timeout parameter is expressed in seconds. For more information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> transactionalContentMD5 is specify the transactional md5 for the body, to
|
||||
// be validated by the service. encryptionKey is optional. Specifies the encryption key to use to encrypt the data
|
||||
// provided in the request. If not specified, encryption is performed with the root account encryption key. For more
|
||||
// information, see Encryption at Rest for Azure Storage Services. encryptionKeySha256 is the SHA-256 hash of the
|
||||
// provided encryption key. Must be provided if the x-ms-encryption-key header is provided. encryptionAlgorithm is the
|
||||
// algorithm used to produce the encryption key hash. Currently, the only accepted value is "AES256". Must be provided
|
||||
// if the x-ms-encryption-key header is provided. leaseID is if specified, the operation only succeeds if the
|
||||
// resource's lease is active and matches this ID. maxSize is optional conditional header. The max length in bytes
|
||||
// permitted for the append blob. If the Append Block operation would cause the blob to exceed that limit or if the
|
||||
// blob size is already greater than the value specified in this header, the request will fail with
|
||||
// MaxBlobSizeConditionNotMet error (HTTP status code 412 - Precondition Failed). appendPosition is optional
|
||||
// conditional header, used only for the Append Block operation. A number indicating the byte offset to compare. Append
|
||||
// Block will succeed only if the append position is equal to this number. If it is not, the request will fail with the
|
||||
// AppendPositionConditionNotMet error (HTTP status code 412 - Precondition Failed). ifModifiedSince is specify this
|
||||
// header value to operate only on a blob if it has been modified since the specified date/time. ifUnmodifiedSince is
|
||||
// specify this header value to operate only on a blob if it has not been modified since the specified date/time.
|
||||
// ifMatch is specify an ETag value to operate only on blobs with a matching value. ifNoneMatch is specify an ETag
|
||||
// value to operate only on blobs without a matching value. sourceIfModifiedSince is specify this header value to
|
||||
// operate only on a blob if it has been modified since the specified date/time. sourceIfUnmodifiedSince is specify
|
||||
// this header value to operate only on a blob if it has not been modified since the specified date/time. sourceIfMatch
|
||||
// is specify an ETag value to operate only on blobs with a matching value. sourceIfNoneMatch is specify an ETag value
|
||||
// to operate only on blobs without a matching value. requestID is provides a client-generated, opaque value with a 1
|
||||
// KB character limit that is recorded in the analytics logs when storage analytics logging is enabled.
|
||||
func (client appendBlobClient) AppendBlockFromURL(ctx context.Context, sourceURL string, contentLength int64, sourceRange *string, sourceContentMD5 []byte, sourceContentcrc64 []byte, timeout *int32, transactionalContentMD5 []byte, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, leaseID *string, maxSize *int64, appendPosition *int64, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, sourceIfModifiedSince *time.Time, sourceIfUnmodifiedSince *time.Time, sourceIfMatch *ETag, sourceIfNoneMatch *ETag, requestID *string) (*AppendBlobAppendBlockFromURLResponse, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.appendBlockFromURLPreparer(sourceURL, contentLength, sourceRange, sourceContentMD5, sourceContentcrc64, timeout, transactionalContentMD5, encryptionKey, encryptionKeySha256, encryptionAlgorithm, leaseID, maxSize, appendPosition, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, sourceIfModifiedSince, sourceIfUnmodifiedSince, sourceIfMatch, sourceIfNoneMatch, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.appendBlockFromURLResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*AppendBlobAppendBlockFromURLResponse), err
|
||||
}
|
||||
|
||||
// appendBlockFromURLPreparer prepares the AppendBlockFromURL request.
|
||||
func (client appendBlobClient) appendBlockFromURLPreparer(sourceURL string, contentLength int64, sourceRange *string, sourceContentMD5 []byte, sourceContentcrc64 []byte, timeout *int32, transactionalContentMD5 []byte, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, leaseID *string, maxSize *int64, appendPosition *int64, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, sourceIfModifiedSince *time.Time, sourceIfUnmodifiedSince *time.Time, sourceIfMatch *ETag, sourceIfNoneMatch *ETag, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("PUT", client.url, nil)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
params.Set("comp", "appendblock")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
req.Header.Set("x-ms-copy-source", sourceURL)
|
||||
if sourceRange != nil {
|
||||
req.Header.Set("x-ms-source-range", *sourceRange)
|
||||
}
|
||||
if sourceContentMD5 != nil {
|
||||
req.Header.Set("x-ms-source-content-md5", base64.StdEncoding.EncodeToString(sourceContentMD5))
|
||||
}
|
||||
if sourceContentcrc64 != nil {
|
||||
req.Header.Set("x-ms-source-content-crc64", base64.StdEncoding.EncodeToString(sourceContentcrc64))
|
||||
}
|
||||
req.Header.Set("Content-Length", strconv.FormatInt(contentLength, 10))
|
||||
if transactionalContentMD5 != nil {
|
||||
req.Header.Set("Content-MD5", base64.StdEncoding.EncodeToString(transactionalContentMD5))
|
||||
}
|
||||
if encryptionKey != nil {
|
||||
req.Header.Set("x-ms-encryption-key", *encryptionKey)
|
||||
}
|
||||
if encryptionKeySha256 != nil {
|
||||
req.Header.Set("x-ms-encryption-key-sha256", *encryptionKeySha256)
|
||||
}
|
||||
if encryptionAlgorithm != EncryptionAlgorithmNone {
|
||||
req.Header.Set("x-ms-encryption-algorithm", string(encryptionAlgorithm))
|
||||
}
|
||||
if leaseID != nil {
|
||||
req.Header.Set("x-ms-lease-id", *leaseID)
|
||||
}
|
||||
if maxSize != nil {
|
||||
req.Header.Set("x-ms-blob-condition-maxsize", strconv.FormatInt(*maxSize, 10))
|
||||
}
|
||||
if appendPosition != nil {
|
||||
req.Header.Set("x-ms-blob-condition-appendpos", strconv.FormatInt(*appendPosition, 10))
|
||||
}
|
||||
if ifModifiedSince != nil {
|
||||
req.Header.Set("If-Modified-Since", (*ifModifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifUnmodifiedSince != nil {
|
||||
req.Header.Set("If-Unmodified-Since", (*ifUnmodifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifMatch != nil {
|
||||
req.Header.Set("If-Match", string(*ifMatch))
|
||||
}
|
||||
if ifNoneMatch != nil {
|
||||
req.Header.Set("If-None-Match", string(*ifNoneMatch))
|
||||
}
|
||||
if sourceIfModifiedSince != nil {
|
||||
req.Header.Set("x-ms-source-if-modified-since", (*sourceIfModifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if sourceIfUnmodifiedSince != nil {
|
||||
req.Header.Set("x-ms-source-if-unmodified-since", (*sourceIfUnmodifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if sourceIfMatch != nil {
|
||||
req.Header.Set("x-ms-source-if-match", string(*sourceIfMatch))
|
||||
}
|
||||
if sourceIfNoneMatch != nil {
|
||||
req.Header.Set("x-ms-source-if-none-match", string(*sourceIfNoneMatch))
|
||||
}
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// appendBlockFromURLResponder handles the response to the AppendBlockFromURL request.
|
||||
func (client appendBlobClient) appendBlockFromURLResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK, http.StatusCreated)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(ioutil.Discard, resp.Response().Body)
|
||||
resp.Response().Body.Close()
|
||||
return &AppendBlobAppendBlockFromURLResponse{rawResponse: resp.Response()}, err
|
||||
}
|
||||
|
||||
// Create the Create Append Blob operation creates a new append blob.
|
||||
//
|
||||
// contentLength is the length of the request. timeout is the timeout parameter is expressed in seconds. For more
|
||||
// information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> blobContentType is optional. Sets the blob's content type. If specified,
|
||||
// this property is stored with the blob and returned with a read request. blobContentEncoding is optional. Sets the
|
||||
// blob's content encoding. If specified, this property is stored with the blob and returned with a read request.
|
||||
// blobContentLanguage is optional. Set the blob's content language. If specified, this property is stored with the
|
||||
// blob and returned with a read request. blobContentMD5 is optional. An MD5 hash of the blob content. Note that this
|
||||
// hash is not validated, as the hashes for the individual blocks were validated when each was uploaded.
|
||||
// blobCacheControl is optional. Sets the blob's cache control. If specified, this property is stored with the blob and
|
||||
// returned with a read request. metadata is optional. Specifies a user-defined name-value pair associated with the
|
||||
// blob. If no name-value pairs are specified, the operation will copy the metadata from the source blob or file to the
|
||||
// destination blob. If one or more name-value pairs are specified, the destination blob is created with the specified
|
||||
// metadata, and metadata is not copied from the source blob or file. Note that beginning with version 2009-09-19,
|
||||
// metadata names must adhere to the naming rules for C# identifiers. See Naming and Referencing Containers, Blobs, and
|
||||
// Metadata for more information. leaseID is if specified, the operation only succeeds if the resource's lease is
|
||||
// active and matches this ID. blobContentDisposition is optional. Sets the blob's Content-Disposition header.
|
||||
// encryptionKey is optional. Specifies the encryption key to use to encrypt the data provided in the request. If not
|
||||
// specified, encryption is performed with the root account encryption key. For more information, see Encryption at
|
||||
// Rest for Azure Storage Services. encryptionKeySha256 is the SHA-256 hash of the provided encryption key. Must be
|
||||
// provided if the x-ms-encryption-key header is provided. encryptionAlgorithm is the algorithm used to produce the
|
||||
// encryption key hash. Currently, the only accepted value is "AES256". Must be provided if the x-ms-encryption-key
|
||||
// header is provided. ifModifiedSince is specify this header value to operate only on a blob if it has been modified
|
||||
// since the specified date/time. ifUnmodifiedSince is specify this header value to operate only on a blob if it has
|
||||
// not been modified since the specified date/time. ifMatch is specify an ETag value to operate only on blobs with a
|
||||
// matching value. ifNoneMatch is specify an ETag value to operate only on blobs without a matching value. requestID is
|
||||
// provides a client-generated, opaque value with a 1 KB character limit that is recorded in the analytics logs when
|
||||
// storage analytics logging is enabled.
|
||||
func (client appendBlobClient) Create(ctx context.Context, contentLength int64, timeout *int32, blobContentType *string, blobContentEncoding *string, blobContentLanguage *string, blobContentMD5 []byte, blobCacheControl *string, metadata map[string]string, leaseID *string, blobContentDisposition *string, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (*AppendBlobCreateResponse, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.createPreparer(contentLength, timeout, blobContentType, blobContentEncoding, blobContentLanguage, blobContentMD5, blobCacheControl, metadata, leaseID, blobContentDisposition, encryptionKey, encryptionKeySha256, encryptionAlgorithm, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.createResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*AppendBlobCreateResponse), err
|
||||
}
|
||||
|
||||
// createPreparer prepares the Create request.
|
||||
func (client appendBlobClient) createPreparer(contentLength int64, timeout *int32, blobContentType *string, blobContentEncoding *string, blobContentLanguage *string, blobContentMD5 []byte, blobCacheControl *string, metadata map[string]string, leaseID *string, blobContentDisposition *string, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("PUT", client.url, nil)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
req.URL.RawQuery = params.Encode()
|
||||
req.Header.Set("Content-Length", strconv.FormatInt(contentLength, 10))
|
||||
if blobContentType != nil {
|
||||
req.Header.Set("x-ms-blob-content-type", *blobContentType)
|
||||
}
|
||||
if blobContentEncoding != nil {
|
||||
req.Header.Set("x-ms-blob-content-encoding", *blobContentEncoding)
|
||||
}
|
||||
if blobContentLanguage != nil {
|
||||
req.Header.Set("x-ms-blob-content-language", *blobContentLanguage)
|
||||
}
|
||||
if blobContentMD5 != nil {
|
||||
req.Header.Set("x-ms-blob-content-md5", base64.StdEncoding.EncodeToString(blobContentMD5))
|
||||
}
|
||||
if blobCacheControl != nil {
|
||||
req.Header.Set("x-ms-blob-cache-control", *blobCacheControl)
|
||||
}
|
||||
if metadata != nil {
|
||||
for k, v := range metadata {
|
||||
req.Header.Set("x-ms-meta-"+k, v)
|
||||
}
|
||||
}
|
||||
if leaseID != nil {
|
||||
req.Header.Set("x-ms-lease-id", *leaseID)
|
||||
}
|
||||
if blobContentDisposition != nil {
|
||||
req.Header.Set("x-ms-blob-content-disposition", *blobContentDisposition)
|
||||
}
|
||||
if encryptionKey != nil {
|
||||
req.Header.Set("x-ms-encryption-key", *encryptionKey)
|
||||
}
|
||||
if encryptionKeySha256 != nil {
|
||||
req.Header.Set("x-ms-encryption-key-sha256", *encryptionKeySha256)
|
||||
}
|
||||
if encryptionAlgorithm != EncryptionAlgorithmNone {
|
||||
req.Header.Set("x-ms-encryption-algorithm", string(encryptionAlgorithm))
|
||||
}
|
||||
if ifModifiedSince != nil {
|
||||
req.Header.Set("If-Modified-Since", (*ifModifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifUnmodifiedSince != nil {
|
||||
req.Header.Set("If-Unmodified-Since", (*ifUnmodifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifMatch != nil {
|
||||
req.Header.Set("If-Match", string(*ifMatch))
|
||||
}
|
||||
if ifNoneMatch != nil {
|
||||
req.Header.Set("If-None-Match", string(*ifNoneMatch))
|
||||
}
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
req.Header.Set("x-ms-blob-type", "AppendBlob")
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// createResponder handles the response to the Create request.
|
||||
func (client appendBlobClient) createResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK, http.StatusCreated)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(ioutil.Discard, resp.Response().Body)
|
||||
resp.Response().Body.Close()
|
||||
return &AppendBlobCreateResponse{rawResponse: resp.Response()}, err
|
||||
}
|
1620
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_blob.go
generated
vendored
Normal file
1620
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_blob.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
592
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_block_blob.go
generated
vendored
Normal file
592
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_block_blob.go
generated
vendored
Normal file
|
@ -0,0 +1,592 @@
|
|||
package azblob
|
||||
|
||||
// Code generated by Microsoft (R) AutoRest Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// blockBlobClient is the client for the BlockBlob methods of the Azblob service.
|
||||
type blockBlobClient struct {
|
||||
managementClient
|
||||
}
|
||||
|
||||
// newBlockBlobClient creates an instance of the blockBlobClient client.
|
||||
func newBlockBlobClient(url url.URL, p pipeline.Pipeline) blockBlobClient {
|
||||
return blockBlobClient{newManagementClient(url, p)}
|
||||
}
|
||||
|
||||
// CommitBlockList the Commit Block List operation writes a blob by specifying the list of block IDs that make up the
|
||||
// blob. In order to be written as part of a blob, a block must have been successfully written to the server in a prior
|
||||
// Put Block operation. You can call Put Block List to update a blob by uploading only those blocks that have changed,
|
||||
// then committing the new and existing blocks together. You can do this by specifying whether to commit a block from
|
||||
// the committed block list or from the uncommitted block list, or to commit the most recently uploaded version of the
|
||||
// block, whichever list it may belong to.
|
||||
//
|
||||
// timeout is the timeout parameter is expressed in seconds. For more information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> blobCacheControl is optional. Sets the blob's cache control. If specified,
|
||||
// this property is stored with the blob and returned with a read request. blobContentType is optional. Sets the blob's
|
||||
// content type. If specified, this property is stored with the blob and returned with a read request.
|
||||
// blobContentEncoding is optional. Sets the blob's content encoding. If specified, this property is stored with the
|
||||
// blob and returned with a read request. blobContentLanguage is optional. Set the blob's content language. If
|
||||
// specified, this property is stored with the blob and returned with a read request. blobContentMD5 is optional. An
|
||||
// MD5 hash of the blob content. Note that this hash is not validated, as the hashes for the individual blocks were
|
||||
// validated when each was uploaded. transactionalContentMD5 is specify the transactional md5 for the body, to be
|
||||
// validated by the service. transactionalContentCrc64 is specify the transactional crc64 for the body, to be validated
|
||||
// by the service. metadata is optional. Specifies a user-defined name-value pair associated with the blob. If no
|
||||
// name-value pairs are specified, the operation will copy the metadata from the source blob or file to the destination
|
||||
// blob. If one or more name-value pairs are specified, the destination blob is created with the specified metadata,
|
||||
// and metadata is not copied from the source blob or file. Note that beginning with version 2009-09-19, metadata names
|
||||
// must adhere to the naming rules for C# identifiers. See Naming and Referencing Containers, Blobs, and Metadata for
|
||||
// more information. leaseID is if specified, the operation only succeeds if the resource's lease is active and matches
|
||||
// this ID. blobContentDisposition is optional. Sets the blob's Content-Disposition header. encryptionKey is optional.
|
||||
// Specifies the encryption key to use to encrypt the data provided in the request. If not specified, encryption is
|
||||
// performed with the root account encryption key. For more information, see Encryption at Rest for Azure Storage
|
||||
// Services. encryptionKeySha256 is the SHA-256 hash of the provided encryption key. Must be provided if the
|
||||
// x-ms-encryption-key header is provided. encryptionAlgorithm is the algorithm used to produce the encryption key
|
||||
// hash. Currently, the only accepted value is "AES256". Must be provided if the x-ms-encryption-key header is
|
||||
// provided. tier is optional. Indicates the tier to be set on the blob. ifModifiedSince is specify this header value
|
||||
// to operate only on a blob if it has been modified since the specified date/time. ifUnmodifiedSince is specify this
|
||||
// header value to operate only on a blob if it has not been modified since the specified date/time. ifMatch is specify
|
||||
// an ETag value to operate only on blobs with a matching value. ifNoneMatch is specify an ETag value to operate only
|
||||
// on blobs without a matching value. requestID is provides a client-generated, opaque value with a 1 KB character
|
||||
// limit that is recorded in the analytics logs when storage analytics logging is enabled.
|
||||
func (client blockBlobClient) CommitBlockList(ctx context.Context, blocks BlockLookupList, timeout *int32, blobCacheControl *string, blobContentType *string, blobContentEncoding *string, blobContentLanguage *string, blobContentMD5 []byte, transactionalContentMD5 []byte, transactionalContentCrc64 []byte, metadata map[string]string, leaseID *string, blobContentDisposition *string, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, tier AccessTierType, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (*BlockBlobCommitBlockListResponse, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.commitBlockListPreparer(blocks, timeout, blobCacheControl, blobContentType, blobContentEncoding, blobContentLanguage, blobContentMD5, transactionalContentMD5, transactionalContentCrc64, metadata, leaseID, blobContentDisposition, encryptionKey, encryptionKeySha256, encryptionAlgorithm, tier, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.commitBlockListResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*BlockBlobCommitBlockListResponse), err
|
||||
}
|
||||
|
||||
// commitBlockListPreparer prepares the CommitBlockList request.
|
||||
func (client blockBlobClient) commitBlockListPreparer(blocks BlockLookupList, timeout *int32, blobCacheControl *string, blobContentType *string, blobContentEncoding *string, blobContentLanguage *string, blobContentMD5 []byte, transactionalContentMD5 []byte, transactionalContentCrc64 []byte, metadata map[string]string, leaseID *string, blobContentDisposition *string, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, tier AccessTierType, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("PUT", client.url, nil)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
params.Set("comp", "blocklist")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
if blobCacheControl != nil {
|
||||
req.Header.Set("x-ms-blob-cache-control", *blobCacheControl)
|
||||
}
|
||||
if blobContentType != nil {
|
||||
req.Header.Set("x-ms-blob-content-type", *blobContentType)
|
||||
}
|
||||
if blobContentEncoding != nil {
|
||||
req.Header.Set("x-ms-blob-content-encoding", *blobContentEncoding)
|
||||
}
|
||||
if blobContentLanguage != nil {
|
||||
req.Header.Set("x-ms-blob-content-language", *blobContentLanguage)
|
||||
}
|
||||
if blobContentMD5 != nil {
|
||||
req.Header.Set("x-ms-blob-content-md5", base64.StdEncoding.EncodeToString(blobContentMD5))
|
||||
}
|
||||
if transactionalContentMD5 != nil {
|
||||
req.Header.Set("Content-MD5", base64.StdEncoding.EncodeToString(transactionalContentMD5))
|
||||
}
|
||||
if transactionalContentCrc64 != nil {
|
||||
req.Header.Set("x-ms-content-crc64", base64.StdEncoding.EncodeToString(transactionalContentCrc64))
|
||||
}
|
||||
if metadata != nil {
|
||||
for k, v := range metadata {
|
||||
req.Header.Set("x-ms-meta-"+k, v)
|
||||
}
|
||||
}
|
||||
if leaseID != nil {
|
||||
req.Header.Set("x-ms-lease-id", *leaseID)
|
||||
}
|
||||
if blobContentDisposition != nil {
|
||||
req.Header.Set("x-ms-blob-content-disposition", *blobContentDisposition)
|
||||
}
|
||||
if encryptionKey != nil {
|
||||
req.Header.Set("x-ms-encryption-key", *encryptionKey)
|
||||
}
|
||||
if encryptionKeySha256 != nil {
|
||||
req.Header.Set("x-ms-encryption-key-sha256", *encryptionKeySha256)
|
||||
}
|
||||
if encryptionAlgorithm != EncryptionAlgorithmNone {
|
||||
req.Header.Set("x-ms-encryption-algorithm", string(encryptionAlgorithm))
|
||||
}
|
||||
if tier != AccessTierNone {
|
||||
req.Header.Set("x-ms-access-tier", string(tier))
|
||||
}
|
||||
if ifModifiedSince != nil {
|
||||
req.Header.Set("If-Modified-Since", (*ifModifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifUnmodifiedSince != nil {
|
||||
req.Header.Set("If-Unmodified-Since", (*ifUnmodifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifMatch != nil {
|
||||
req.Header.Set("If-Match", string(*ifMatch))
|
||||
}
|
||||
if ifNoneMatch != nil {
|
||||
req.Header.Set("If-None-Match", string(*ifNoneMatch))
|
||||
}
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
b, err := xml.Marshal(blocks)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to marshal request body")
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/xml")
|
||||
err = req.SetBody(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to set request body")
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// commitBlockListResponder handles the response to the CommitBlockList request.
|
||||
func (client blockBlobClient) commitBlockListResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK, http.StatusCreated)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(ioutil.Discard, resp.Response().Body)
|
||||
resp.Response().Body.Close()
|
||||
return &BlockBlobCommitBlockListResponse{rawResponse: resp.Response()}, err
|
||||
}
|
||||
|
||||
// GetBlockList the Get Block List operation retrieves the list of blocks that have been uploaded as part of a block
|
||||
// blob
|
||||
//
|
||||
// listType is specifies whether to return the list of committed blocks, the list of uncommitted blocks, or both lists
|
||||
// together. snapshot is the snapshot parameter is an opaque DateTime value that, when present, specifies the blob
|
||||
// snapshot to retrieve. For more information on working with blob snapshots, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/creating-a-snapshot-of-a-blob">Creating
|
||||
// a Snapshot of a Blob.</a> timeout is the timeout parameter is expressed in seconds. For more information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> leaseID is if specified, the operation only succeeds if the resource's
|
||||
// lease is active and matches this ID. requestID is provides a client-generated, opaque value with a 1 KB character
|
||||
// limit that is recorded in the analytics logs when storage analytics logging is enabled.
|
||||
func (client blockBlobClient) GetBlockList(ctx context.Context, listType BlockListType, snapshot *string, timeout *int32, leaseID *string, requestID *string) (*BlockList, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.getBlockListPreparer(listType, snapshot, timeout, leaseID, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.getBlockListResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*BlockList), err
|
||||
}
|
||||
|
||||
// getBlockListPreparer prepares the GetBlockList request.
|
||||
func (client blockBlobClient) getBlockListPreparer(listType BlockListType, snapshot *string, timeout *int32, leaseID *string, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("GET", client.url, nil)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if snapshot != nil && len(*snapshot) > 0 {
|
||||
params.Set("snapshot", *snapshot)
|
||||
}
|
||||
params.Set("blocklisttype", string(listType))
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
params.Set("comp", "blocklist")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
if leaseID != nil {
|
||||
req.Header.Set("x-ms-lease-id", *leaseID)
|
||||
}
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// getBlockListResponder handles the response to the GetBlockList request.
|
||||
func (client blockBlobClient) getBlockListResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &BlockList{rawResponse: resp.Response()}
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
defer resp.Response().Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Response().Body)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if len(b) > 0 {
|
||||
b = removeBOM(b)
|
||||
err = xml.Unmarshal(b, result)
|
||||
if err != nil {
|
||||
return result, NewResponseError(err, resp.Response(), "failed to unmarshal response body")
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// StageBlock the Stage Block operation creates a new block to be committed as part of a blob
|
||||
//
|
||||
// blockID is a valid Base64 string value that identifies the block. Prior to encoding, the string must be less than or
|
||||
// equal to 64 bytes in size. For a given blob, the length of the value specified for the blockid parameter must be the
|
||||
// same size for each block. contentLength is the length of the request. body is initial data body will be closed upon
|
||||
// successful return. Callers should ensure closure when receiving an error.transactionalContentMD5 is specify the
|
||||
// transactional md5 for the body, to be validated by the service. transactionalContentCrc64 is specify the
|
||||
// transactional crc64 for the body, to be validated by the service. timeout is the timeout parameter is expressed in
|
||||
// seconds. For more information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> leaseID is if specified, the operation only succeeds if the resource's
|
||||
// lease is active and matches this ID. encryptionKey is optional. Specifies the encryption key to use to encrypt the
|
||||
// data provided in the request. If not specified, encryption is performed with the root account encryption key. For
|
||||
// more information, see Encryption at Rest for Azure Storage Services. encryptionKeySha256 is the SHA-256 hash of the
|
||||
// provided encryption key. Must be provided if the x-ms-encryption-key header is provided. encryptionAlgorithm is the
|
||||
// algorithm used to produce the encryption key hash. Currently, the only accepted value is "AES256". Must be provided
|
||||
// if the x-ms-encryption-key header is provided. requestID is provides a client-generated, opaque value with a 1 KB
|
||||
// character limit that is recorded in the analytics logs when storage analytics logging is enabled.
|
||||
func (client blockBlobClient) StageBlock(ctx context.Context, blockID string, contentLength int64, body io.ReadSeeker, transactionalContentMD5 []byte, transactionalContentCrc64 []byte, timeout *int32, leaseID *string, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, requestID *string) (*BlockBlobStageBlockResponse, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: body,
|
||||
constraints: []constraint{{target: "body", name: null, rule: true, chain: nil}}},
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.stageBlockPreparer(blockID, contentLength, body, transactionalContentMD5, transactionalContentCrc64, timeout, leaseID, encryptionKey, encryptionKeySha256, encryptionAlgorithm, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.stageBlockResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*BlockBlobStageBlockResponse), err
|
||||
}
|
||||
|
||||
// stageBlockPreparer prepares the StageBlock request.
|
||||
func (client blockBlobClient) stageBlockPreparer(blockID string, contentLength int64, body io.ReadSeeker, transactionalContentMD5 []byte, transactionalContentCrc64 []byte, timeout *int32, leaseID *string, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("PUT", client.url, body)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
params.Set("blockid", blockID)
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
params.Set("comp", "block")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
req.Header.Set("Content-Length", strconv.FormatInt(contentLength, 10))
|
||||
if transactionalContentMD5 != nil {
|
||||
req.Header.Set("Content-MD5", base64.StdEncoding.EncodeToString(transactionalContentMD5))
|
||||
}
|
||||
if transactionalContentCrc64 != nil {
|
||||
req.Header.Set("x-ms-content-crc64", base64.StdEncoding.EncodeToString(transactionalContentCrc64))
|
||||
}
|
||||
if leaseID != nil {
|
||||
req.Header.Set("x-ms-lease-id", *leaseID)
|
||||
}
|
||||
if encryptionKey != nil {
|
||||
req.Header.Set("x-ms-encryption-key", *encryptionKey)
|
||||
}
|
||||
if encryptionKeySha256 != nil {
|
||||
req.Header.Set("x-ms-encryption-key-sha256", *encryptionKeySha256)
|
||||
}
|
||||
if encryptionAlgorithm != EncryptionAlgorithmNone {
|
||||
req.Header.Set("x-ms-encryption-algorithm", string(encryptionAlgorithm))
|
||||
}
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// stageBlockResponder handles the response to the StageBlock request.
|
||||
func (client blockBlobClient) stageBlockResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK, http.StatusCreated)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(ioutil.Discard, resp.Response().Body)
|
||||
resp.Response().Body.Close()
|
||||
return &BlockBlobStageBlockResponse{rawResponse: resp.Response()}, err
|
||||
}
|
||||
|
||||
// StageBlockFromURL the Stage Block operation creates a new block to be committed as part of a blob where the contents
|
||||
// are read from a URL.
|
||||
//
|
||||
// blockID is a valid Base64 string value that identifies the block. Prior to encoding, the string must be less than or
|
||||
// equal to 64 bytes in size. For a given blob, the length of the value specified for the blockid parameter must be the
|
||||
// same size for each block. contentLength is the length of the request. sourceURL is specify a URL to the copy source.
|
||||
// sourceRange is bytes of source data in the specified range. sourceContentMD5 is specify the md5 calculated for the
|
||||
// range of bytes that must be read from the copy source. sourceContentcrc64 is specify the crc64 calculated for the
|
||||
// range of bytes that must be read from the copy source. timeout is the timeout parameter is expressed in seconds. For
|
||||
// more information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> encryptionKey is optional. Specifies the encryption key to use to encrypt
|
||||
// the data provided in the request. If not specified, encryption is performed with the root account encryption key.
|
||||
// For more information, see Encryption at Rest for Azure Storage Services. encryptionKeySha256 is the SHA-256 hash of
|
||||
// the provided encryption key. Must be provided if the x-ms-encryption-key header is provided. encryptionAlgorithm is
|
||||
// the algorithm used to produce the encryption key hash. Currently, the only accepted value is "AES256". Must be
|
||||
// provided if the x-ms-encryption-key header is provided. leaseID is if specified, the operation only succeeds if the
|
||||
// resource's lease is active and matches this ID. sourceIfModifiedSince is specify this header value to operate only
|
||||
// on a blob if it has been modified since the specified date/time. sourceIfUnmodifiedSince is specify this header
|
||||
// value to operate only on a blob if it has not been modified since the specified date/time. sourceIfMatch is specify
|
||||
// an ETag value to operate only on blobs with a matching value. sourceIfNoneMatch is specify an ETag value to operate
|
||||
// only on blobs without a matching value. requestID is provides a client-generated, opaque value with a 1 KB character
|
||||
// limit that is recorded in the analytics logs when storage analytics logging is enabled.
|
||||
func (client blockBlobClient) StageBlockFromURL(ctx context.Context, blockID string, contentLength int64, sourceURL string, sourceRange *string, sourceContentMD5 []byte, sourceContentcrc64 []byte, timeout *int32, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, leaseID *string, sourceIfModifiedSince *time.Time, sourceIfUnmodifiedSince *time.Time, sourceIfMatch *ETag, sourceIfNoneMatch *ETag, requestID *string) (*BlockBlobStageBlockFromURLResponse, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.stageBlockFromURLPreparer(blockID, contentLength, sourceURL, sourceRange, sourceContentMD5, sourceContentcrc64, timeout, encryptionKey, encryptionKeySha256, encryptionAlgorithm, leaseID, sourceIfModifiedSince, sourceIfUnmodifiedSince, sourceIfMatch, sourceIfNoneMatch, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.stageBlockFromURLResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*BlockBlobStageBlockFromURLResponse), err
|
||||
}
|
||||
|
||||
// stageBlockFromURLPreparer prepares the StageBlockFromURL request.
|
||||
func (client blockBlobClient) stageBlockFromURLPreparer(blockID string, contentLength int64, sourceURL string, sourceRange *string, sourceContentMD5 []byte, sourceContentcrc64 []byte, timeout *int32, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, leaseID *string, sourceIfModifiedSince *time.Time, sourceIfUnmodifiedSince *time.Time, sourceIfMatch *ETag, sourceIfNoneMatch *ETag, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("PUT", client.url, nil)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
params.Set("blockid", blockID)
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
params.Set("comp", "block")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
req.Header.Set("Content-Length", strconv.FormatInt(contentLength, 10))
|
||||
req.Header.Set("x-ms-copy-source", sourceURL)
|
||||
if sourceRange != nil {
|
||||
req.Header.Set("x-ms-source-range", *sourceRange)
|
||||
}
|
||||
if sourceContentMD5 != nil {
|
||||
req.Header.Set("x-ms-source-content-md5", base64.StdEncoding.EncodeToString(sourceContentMD5))
|
||||
}
|
||||
if sourceContentcrc64 != nil {
|
||||
req.Header.Set("x-ms-source-content-crc64", base64.StdEncoding.EncodeToString(sourceContentcrc64))
|
||||
}
|
||||
if encryptionKey != nil {
|
||||
req.Header.Set("x-ms-encryption-key", *encryptionKey)
|
||||
}
|
||||
if encryptionKeySha256 != nil {
|
||||
req.Header.Set("x-ms-encryption-key-sha256", *encryptionKeySha256)
|
||||
}
|
||||
if encryptionAlgorithm != EncryptionAlgorithmNone {
|
||||
req.Header.Set("x-ms-encryption-algorithm", string(encryptionAlgorithm))
|
||||
}
|
||||
if leaseID != nil {
|
||||
req.Header.Set("x-ms-lease-id", *leaseID)
|
||||
}
|
||||
if sourceIfModifiedSince != nil {
|
||||
req.Header.Set("x-ms-source-if-modified-since", (*sourceIfModifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if sourceIfUnmodifiedSince != nil {
|
||||
req.Header.Set("x-ms-source-if-unmodified-since", (*sourceIfUnmodifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if sourceIfMatch != nil {
|
||||
req.Header.Set("x-ms-source-if-match", string(*sourceIfMatch))
|
||||
}
|
||||
if sourceIfNoneMatch != nil {
|
||||
req.Header.Set("x-ms-source-if-none-match", string(*sourceIfNoneMatch))
|
||||
}
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// stageBlockFromURLResponder handles the response to the StageBlockFromURL request.
|
||||
func (client blockBlobClient) stageBlockFromURLResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK, http.StatusCreated)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(ioutil.Discard, resp.Response().Body)
|
||||
resp.Response().Body.Close()
|
||||
return &BlockBlobStageBlockFromURLResponse{rawResponse: resp.Response()}, err
|
||||
}
|
||||
|
||||
// Upload the Upload Block Blob operation updates the content of an existing block blob. Updating an existing block
|
||||
// blob overwrites any existing metadata on the blob. Partial updates are not supported with Put Blob; the content of
|
||||
// the existing blob is overwritten with the content of the new blob. To perform a partial update of the content of a
|
||||
// block blob, use the Put Block List operation.
|
||||
//
|
||||
// body is initial data body will be closed upon successful return. Callers should ensure closure when receiving an
|
||||
// error.contentLength is the length of the request. timeout is the timeout parameter is expressed in seconds. For more
|
||||
// information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> transactionalContentMD5 is specify the transactional md5 for the body, to
|
||||
// be validated by the service. blobContentType is optional. Sets the blob's content type. If specified, this property
|
||||
// is stored with the blob and returned with a read request. blobContentEncoding is optional. Sets the blob's content
|
||||
// encoding. If specified, this property is stored with the blob and returned with a read request. blobContentLanguage
|
||||
// is optional. Set the blob's content language. If specified, this property is stored with the blob and returned with
|
||||
// a read request. blobContentMD5 is optional. An MD5 hash of the blob content. Note that this hash is not validated,
|
||||
// as the hashes for the individual blocks were validated when each was uploaded. blobCacheControl is optional. Sets
|
||||
// the blob's cache control. If specified, this property is stored with the blob and returned with a read request.
|
||||
// metadata is optional. Specifies a user-defined name-value pair associated with the blob. If no name-value pairs are
|
||||
// specified, the operation will copy the metadata from the source blob or file to the destination blob. If one or more
|
||||
// name-value pairs are specified, the destination blob is created with the specified metadata, and metadata is not
|
||||
// copied from the source blob or file. Note that beginning with version 2009-09-19, metadata names must adhere to the
|
||||
// naming rules for C# identifiers. See Naming and Referencing Containers, Blobs, and Metadata for more information.
|
||||
// leaseID is if specified, the operation only succeeds if the resource's lease is active and matches this ID.
|
||||
// blobContentDisposition is optional. Sets the blob's Content-Disposition header. encryptionKey is optional. Specifies
|
||||
// the encryption key to use to encrypt the data provided in the request. If not specified, encryption is performed
|
||||
// with the root account encryption key. For more information, see Encryption at Rest for Azure Storage Services.
|
||||
// encryptionKeySha256 is the SHA-256 hash of the provided encryption key. Must be provided if the x-ms-encryption-key
|
||||
// header is provided. encryptionAlgorithm is the algorithm used to produce the encryption key hash. Currently, the
|
||||
// only accepted value is "AES256". Must be provided if the x-ms-encryption-key header is provided. tier is optional.
|
||||
// Indicates the tier to be set on the blob. ifModifiedSince is specify this header value to operate only on a blob if
|
||||
// it has been modified since the specified date/time. ifUnmodifiedSince is specify this header value to operate only
|
||||
// on a blob if it has not been modified since the specified date/time. ifMatch is specify an ETag value to operate
|
||||
// only on blobs with a matching value. ifNoneMatch is specify an ETag value to operate only on blobs without a
|
||||
// matching value. requestID is provides a client-generated, opaque value with a 1 KB character limit that is recorded
|
||||
// in the analytics logs when storage analytics logging is enabled.
|
||||
func (client blockBlobClient) Upload(ctx context.Context, body io.ReadSeeker, contentLength int64, timeout *int32, transactionalContentMD5 []byte, blobContentType *string, blobContentEncoding *string, blobContentLanguage *string, blobContentMD5 []byte, blobCacheControl *string, metadata map[string]string, leaseID *string, blobContentDisposition *string, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, tier AccessTierType, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (*BlockBlobUploadResponse, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: body,
|
||||
constraints: []constraint{{target: "body", name: null, rule: true, chain: nil}}},
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.uploadPreparer(body, contentLength, timeout, transactionalContentMD5, blobContentType, blobContentEncoding, blobContentLanguage, blobContentMD5, blobCacheControl, metadata, leaseID, blobContentDisposition, encryptionKey, encryptionKeySha256, encryptionAlgorithm, tier, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.uploadResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*BlockBlobUploadResponse), err
|
||||
}
|
||||
|
||||
// uploadPreparer prepares the Upload request.
|
||||
func (client blockBlobClient) uploadPreparer(body io.ReadSeeker, contentLength int64, timeout *int32, transactionalContentMD5 []byte, blobContentType *string, blobContentEncoding *string, blobContentLanguage *string, blobContentMD5 []byte, blobCacheControl *string, metadata map[string]string, leaseID *string, blobContentDisposition *string, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, tier AccessTierType, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("PUT", client.url, body)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
req.URL.RawQuery = params.Encode()
|
||||
if transactionalContentMD5 != nil {
|
||||
req.Header.Set("Content-MD5", base64.StdEncoding.EncodeToString(transactionalContentMD5))
|
||||
}
|
||||
req.Header.Set("Content-Length", strconv.FormatInt(contentLength, 10))
|
||||
if blobContentType != nil {
|
||||
req.Header.Set("x-ms-blob-content-type", *blobContentType)
|
||||
}
|
||||
if blobContentEncoding != nil {
|
||||
req.Header.Set("x-ms-blob-content-encoding", *blobContentEncoding)
|
||||
}
|
||||
if blobContentLanguage != nil {
|
||||
req.Header.Set("x-ms-blob-content-language", *blobContentLanguage)
|
||||
}
|
||||
if blobContentMD5 != nil {
|
||||
req.Header.Set("x-ms-blob-content-md5", base64.StdEncoding.EncodeToString(blobContentMD5))
|
||||
}
|
||||
if blobCacheControl != nil {
|
||||
req.Header.Set("x-ms-blob-cache-control", *blobCacheControl)
|
||||
}
|
||||
if metadata != nil {
|
||||
for k, v := range metadata {
|
||||
req.Header.Set("x-ms-meta-"+k, v)
|
||||
}
|
||||
}
|
||||
if leaseID != nil {
|
||||
req.Header.Set("x-ms-lease-id", *leaseID)
|
||||
}
|
||||
if blobContentDisposition != nil {
|
||||
req.Header.Set("x-ms-blob-content-disposition", *blobContentDisposition)
|
||||
}
|
||||
if encryptionKey != nil {
|
||||
req.Header.Set("x-ms-encryption-key", *encryptionKey)
|
||||
}
|
||||
if encryptionKeySha256 != nil {
|
||||
req.Header.Set("x-ms-encryption-key-sha256", *encryptionKeySha256)
|
||||
}
|
||||
if encryptionAlgorithm != EncryptionAlgorithmNone {
|
||||
req.Header.Set("x-ms-encryption-algorithm", string(encryptionAlgorithm))
|
||||
}
|
||||
if tier != AccessTierNone {
|
||||
req.Header.Set("x-ms-access-tier", string(tier))
|
||||
}
|
||||
if ifModifiedSince != nil {
|
||||
req.Header.Set("If-Modified-Since", (*ifModifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifUnmodifiedSince != nil {
|
||||
req.Header.Set("If-Unmodified-Since", (*ifUnmodifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifMatch != nil {
|
||||
req.Header.Set("If-Match", string(*ifMatch))
|
||||
}
|
||||
if ifNoneMatch != nil {
|
||||
req.Header.Set("If-None-Match", string(*ifNoneMatch))
|
||||
}
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
req.Header.Set("x-ms-blob-type", "BlockBlob")
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// uploadResponder handles the response to the Upload request.
|
||||
func (client blockBlobClient) uploadResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK, http.StatusCreated)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(ioutil.Discard, resp.Response().Body)
|
||||
resp.Response().Body.Close()
|
||||
return &BlockBlobUploadResponse{rawResponse: resp.Response()}, err
|
||||
}
|
38
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_client.go
generated
vendored
Normal file
38
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_client.go
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
package azblob
|
||||
|
||||
// Code generated by Microsoft (R) AutoRest Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
import (
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const (
|
||||
// ServiceVersion specifies the version of the operations used in this package.
|
||||
ServiceVersion = "2019-02-02"
|
||||
)
|
||||
|
||||
// managementClient is the base client for Azblob.
|
||||
type managementClient struct {
|
||||
url url.URL
|
||||
p pipeline.Pipeline
|
||||
}
|
||||
|
||||
// newManagementClient creates an instance of the managementClient client.
|
||||
func newManagementClient(url url.URL, p pipeline.Pipeline) managementClient {
|
||||
return managementClient{
|
||||
url: url,
|
||||
p: p,
|
||||
}
|
||||
}
|
||||
|
||||
// URL returns a copy of the URL for this client.
|
||||
func (mc managementClient) URL() url.URL {
|
||||
return mc.url
|
||||
}
|
||||
|
||||
// Pipeline returns the pipeline for this client.
|
||||
func (mc managementClient) Pipeline() pipeline.Pipeline {
|
||||
return mc.p
|
||||
}
|
1037
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_container.go
generated
vendored
Normal file
1037
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_container.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
6421
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_models.go
generated
vendored
Normal file
6421
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_models.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
977
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_page_blob.go
generated
vendored
Normal file
977
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_page_blob.go
generated
vendored
Normal file
|
@ -0,0 +1,977 @@
|
|||
package azblob
|
||||
|
||||
// Code generated by Microsoft (R) AutoRest Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// pageBlobClient is the client for the PageBlob methods of the Azblob service.
|
||||
type pageBlobClient struct {
|
||||
managementClient
|
||||
}
|
||||
|
||||
// newPageBlobClient creates an instance of the pageBlobClient client.
|
||||
func newPageBlobClient(url url.URL, p pipeline.Pipeline) pageBlobClient {
|
||||
return pageBlobClient{newManagementClient(url, p)}
|
||||
}
|
||||
|
||||
// ClearPages the Clear Pages operation clears a set of pages from a page blob
|
||||
//
|
||||
// contentLength is the length of the request. timeout is the timeout parameter is expressed in seconds. For more
|
||||
// information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> rangeParameter is return only the bytes of the blob in the specified
|
||||
// range. leaseID is if specified, the operation only succeeds if the resource's lease is active and matches this ID.
|
||||
// encryptionKey is optional. Specifies the encryption key to use to encrypt the data provided in the request. If not
|
||||
// specified, encryption is performed with the root account encryption key. For more information, see Encryption at
|
||||
// Rest for Azure Storage Services. encryptionKeySha256 is the SHA-256 hash of the provided encryption key. Must be
|
||||
// provided if the x-ms-encryption-key header is provided. encryptionAlgorithm is the algorithm used to produce the
|
||||
// encryption key hash. Currently, the only accepted value is "AES256". Must be provided if the x-ms-encryption-key
|
||||
// header is provided. ifSequenceNumberLessThanOrEqualTo is specify this header value to operate only on a blob if it
|
||||
// has a sequence number less than or equal to the specified. ifSequenceNumberLessThan is specify this header value to
|
||||
// operate only on a blob if it has a sequence number less than the specified. ifSequenceNumberEqualTo is specify this
|
||||
// header value to operate only on a blob if it has the specified sequence number. ifModifiedSince is specify this
|
||||
// header value to operate only on a blob if it has been modified since the specified date/time. ifUnmodifiedSince is
|
||||
// specify this header value to operate only on a blob if it has not been modified since the specified date/time.
|
||||
// ifMatch is specify an ETag value to operate only on blobs with a matching value. ifNoneMatch is specify an ETag
|
||||
// value to operate only on blobs without a matching value. requestID is provides a client-generated, opaque value with
|
||||
// a 1 KB character limit that is recorded in the analytics logs when storage analytics logging is enabled.
|
||||
func (client pageBlobClient) ClearPages(ctx context.Context, contentLength int64, timeout *int32, rangeParameter *string, leaseID *string, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, ifSequenceNumberLessThanOrEqualTo *int64, ifSequenceNumberLessThan *int64, ifSequenceNumberEqualTo *int64, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (*PageBlobClearPagesResponse, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.clearPagesPreparer(contentLength, timeout, rangeParameter, leaseID, encryptionKey, encryptionKeySha256, encryptionAlgorithm, ifSequenceNumberLessThanOrEqualTo, ifSequenceNumberLessThan, ifSequenceNumberEqualTo, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.clearPagesResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*PageBlobClearPagesResponse), err
|
||||
}
|
||||
|
||||
// clearPagesPreparer prepares the ClearPages request.
|
||||
func (client pageBlobClient) clearPagesPreparer(contentLength int64, timeout *int32, rangeParameter *string, leaseID *string, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, ifSequenceNumberLessThanOrEqualTo *int64, ifSequenceNumberLessThan *int64, ifSequenceNumberEqualTo *int64, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("PUT", client.url, nil)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
params.Set("comp", "page")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
req.Header.Set("Content-Length", strconv.FormatInt(contentLength, 10))
|
||||
if rangeParameter != nil {
|
||||
req.Header.Set("x-ms-range", *rangeParameter)
|
||||
}
|
||||
if leaseID != nil {
|
||||
req.Header.Set("x-ms-lease-id", *leaseID)
|
||||
}
|
||||
if encryptionKey != nil {
|
||||
req.Header.Set("x-ms-encryption-key", *encryptionKey)
|
||||
}
|
||||
if encryptionKeySha256 != nil {
|
||||
req.Header.Set("x-ms-encryption-key-sha256", *encryptionKeySha256)
|
||||
}
|
||||
if encryptionAlgorithm != EncryptionAlgorithmNone {
|
||||
req.Header.Set("x-ms-encryption-algorithm", string(encryptionAlgorithm))
|
||||
}
|
||||
if ifSequenceNumberLessThanOrEqualTo != nil {
|
||||
req.Header.Set("x-ms-if-sequence-number-le", strconv.FormatInt(*ifSequenceNumberLessThanOrEqualTo, 10))
|
||||
}
|
||||
if ifSequenceNumberLessThan != nil {
|
||||
req.Header.Set("x-ms-if-sequence-number-lt", strconv.FormatInt(*ifSequenceNumberLessThan, 10))
|
||||
}
|
||||
if ifSequenceNumberEqualTo != nil {
|
||||
req.Header.Set("x-ms-if-sequence-number-eq", strconv.FormatInt(*ifSequenceNumberEqualTo, 10))
|
||||
}
|
||||
if ifModifiedSince != nil {
|
||||
req.Header.Set("If-Modified-Since", (*ifModifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifUnmodifiedSince != nil {
|
||||
req.Header.Set("If-Unmodified-Since", (*ifUnmodifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifMatch != nil {
|
||||
req.Header.Set("If-Match", string(*ifMatch))
|
||||
}
|
||||
if ifNoneMatch != nil {
|
||||
req.Header.Set("If-None-Match", string(*ifNoneMatch))
|
||||
}
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
req.Header.Set("x-ms-page-write", "clear")
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// clearPagesResponder handles the response to the ClearPages request.
|
||||
func (client pageBlobClient) clearPagesResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK, http.StatusCreated)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(ioutil.Discard, resp.Response().Body)
|
||||
resp.Response().Body.Close()
|
||||
return &PageBlobClearPagesResponse{rawResponse: resp.Response()}, err
|
||||
}
|
||||
|
||||
// CopyIncremental the Copy Incremental operation copies a snapshot of the source page blob to a destination page blob.
|
||||
// The snapshot is copied such that only the differential changes between the previously copied snapshot are
|
||||
// transferred to the destination. The copied snapshots are complete copies of the original snapshot and can be read or
|
||||
// copied from as usual. This API is supported since REST version 2016-05-31.
|
||||
//
|
||||
// copySource is specifies the name of the source page blob snapshot. This value is a URL of up to 2 KB in length that
|
||||
// specifies a page blob snapshot. The value should be URL-encoded as it would appear in a request URI. The source blob
|
||||
// must either be public or must be authenticated via a shared access signature. timeout is the timeout parameter is
|
||||
// expressed in seconds. For more information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> ifModifiedSince is specify this header value to operate only on a blob if
|
||||
// it has been modified since the specified date/time. ifUnmodifiedSince is specify this header value to operate only
|
||||
// on a blob if it has not been modified since the specified date/time. ifMatch is specify an ETag value to operate
|
||||
// only on blobs with a matching value. ifNoneMatch is specify an ETag value to operate only on blobs without a
|
||||
// matching value. requestID is provides a client-generated, opaque value with a 1 KB character limit that is recorded
|
||||
// in the analytics logs when storage analytics logging is enabled.
|
||||
func (client pageBlobClient) CopyIncremental(ctx context.Context, copySource string, timeout *int32, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (*PageBlobCopyIncrementalResponse, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.copyIncrementalPreparer(copySource, timeout, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.copyIncrementalResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*PageBlobCopyIncrementalResponse), err
|
||||
}
|
||||
|
||||
// copyIncrementalPreparer prepares the CopyIncremental request.
|
||||
func (client pageBlobClient) copyIncrementalPreparer(copySource string, timeout *int32, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("PUT", client.url, nil)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
params.Set("comp", "incrementalcopy")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
if ifModifiedSince != nil {
|
||||
req.Header.Set("If-Modified-Since", (*ifModifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifUnmodifiedSince != nil {
|
||||
req.Header.Set("If-Unmodified-Since", (*ifUnmodifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifMatch != nil {
|
||||
req.Header.Set("If-Match", string(*ifMatch))
|
||||
}
|
||||
if ifNoneMatch != nil {
|
||||
req.Header.Set("If-None-Match", string(*ifNoneMatch))
|
||||
}
|
||||
req.Header.Set("x-ms-copy-source", copySource)
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// copyIncrementalResponder handles the response to the CopyIncremental request.
|
||||
func (client pageBlobClient) copyIncrementalResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK, http.StatusAccepted)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(ioutil.Discard, resp.Response().Body)
|
||||
resp.Response().Body.Close()
|
||||
return &PageBlobCopyIncrementalResponse{rawResponse: resp.Response()}, err
|
||||
}
|
||||
|
||||
// Create the Create operation creates a new page blob.
|
||||
//
|
||||
// contentLength is the length of the request. blobContentLength is this header specifies the maximum size for the page
|
||||
// blob, up to 1 TB. The page blob size must be aligned to a 512-byte boundary. timeout is the timeout parameter is
|
||||
// expressed in seconds. For more information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> tier is optional. Indicates the tier to be set on the page blob.
|
||||
// blobContentType is optional. Sets the blob's content type. If specified, this property is stored with the blob and
|
||||
// returned with a read request. blobContentEncoding is optional. Sets the blob's content encoding. If specified, this
|
||||
// property is stored with the blob and returned with a read request. blobContentLanguage is optional. Set the blob's
|
||||
// content language. If specified, this property is stored with the blob and returned with a read request.
|
||||
// blobContentMD5 is optional. An MD5 hash of the blob content. Note that this hash is not validated, as the hashes for
|
||||
// the individual blocks were validated when each was uploaded. blobCacheControl is optional. Sets the blob's cache
|
||||
// control. If specified, this property is stored with the blob and returned with a read request. metadata is optional.
|
||||
// Specifies a user-defined name-value pair associated with the blob. If no name-value pairs are specified, the
|
||||
// operation will copy the metadata from the source blob or file to the destination blob. If one or more name-value
|
||||
// pairs are specified, the destination blob is created with the specified metadata, and metadata is not copied from
|
||||
// the source blob or file. Note that beginning with version 2009-09-19, metadata names must adhere to the naming rules
|
||||
// for C# identifiers. See Naming and Referencing Containers, Blobs, and Metadata for more information. leaseID is if
|
||||
// specified, the operation only succeeds if the resource's lease is active and matches this ID. blobContentDisposition
|
||||
// is optional. Sets the blob's Content-Disposition header. encryptionKey is optional. Specifies the encryption key to
|
||||
// use to encrypt the data provided in the request. If not specified, encryption is performed with the root account
|
||||
// encryption key. For more information, see Encryption at Rest for Azure Storage Services. encryptionKeySha256 is the
|
||||
// SHA-256 hash of the provided encryption key. Must be provided if the x-ms-encryption-key header is provided.
|
||||
// encryptionAlgorithm is the algorithm used to produce the encryption key hash. Currently, the only accepted value is
|
||||
// "AES256". Must be provided if the x-ms-encryption-key header is provided. ifModifiedSince is specify this header
|
||||
// value to operate only on a blob if it has been modified since the specified date/time. ifUnmodifiedSince is specify
|
||||
// this header value to operate only on a blob if it has not been modified since the specified date/time. ifMatch is
|
||||
// specify an ETag value to operate only on blobs with a matching value. ifNoneMatch is specify an ETag value to
|
||||
// operate only on blobs without a matching value. blobSequenceNumber is set for page blobs only. The sequence number
|
||||
// is a user-controlled value that you can use to track requests. The value of the sequence number must be between 0
|
||||
// and 2^63 - 1. requestID is provides a client-generated, opaque value with a 1 KB character limit that is recorded in
|
||||
// the analytics logs when storage analytics logging is enabled.
|
||||
func (client pageBlobClient) Create(ctx context.Context, contentLength int64, blobContentLength int64, timeout *int32, tier PremiumPageBlobAccessTierType, blobContentType *string, blobContentEncoding *string, blobContentLanguage *string, blobContentMD5 []byte, blobCacheControl *string, metadata map[string]string, leaseID *string, blobContentDisposition *string, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, blobSequenceNumber *int64, requestID *string) (*PageBlobCreateResponse, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.createPreparer(contentLength, blobContentLength, timeout, tier, blobContentType, blobContentEncoding, blobContentLanguage, blobContentMD5, blobCacheControl, metadata, leaseID, blobContentDisposition, encryptionKey, encryptionKeySha256, encryptionAlgorithm, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, blobSequenceNumber, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.createResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*PageBlobCreateResponse), err
|
||||
}
|
||||
|
||||
// createPreparer prepares the Create request.
|
||||
func (client pageBlobClient) createPreparer(contentLength int64, blobContentLength int64, timeout *int32, tier PremiumPageBlobAccessTierType, blobContentType *string, blobContentEncoding *string, blobContentLanguage *string, blobContentMD5 []byte, blobCacheControl *string, metadata map[string]string, leaseID *string, blobContentDisposition *string, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, blobSequenceNumber *int64, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("PUT", client.url, nil)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
req.URL.RawQuery = params.Encode()
|
||||
req.Header.Set("Content-Length", strconv.FormatInt(contentLength, 10))
|
||||
if tier != PremiumPageBlobAccessTierNone {
|
||||
req.Header.Set("x-ms-access-tier", string(tier))
|
||||
}
|
||||
if blobContentType != nil {
|
||||
req.Header.Set("x-ms-blob-content-type", *blobContentType)
|
||||
}
|
||||
if blobContentEncoding != nil {
|
||||
req.Header.Set("x-ms-blob-content-encoding", *blobContentEncoding)
|
||||
}
|
||||
if blobContentLanguage != nil {
|
||||
req.Header.Set("x-ms-blob-content-language", *blobContentLanguage)
|
||||
}
|
||||
if blobContentMD5 != nil {
|
||||
req.Header.Set("x-ms-blob-content-md5", base64.StdEncoding.EncodeToString(blobContentMD5))
|
||||
}
|
||||
if blobCacheControl != nil {
|
||||
req.Header.Set("x-ms-blob-cache-control", *blobCacheControl)
|
||||
}
|
||||
if metadata != nil {
|
||||
for k, v := range metadata {
|
||||
req.Header.Set("x-ms-meta-"+k, v)
|
||||
}
|
||||
}
|
||||
if leaseID != nil {
|
||||
req.Header.Set("x-ms-lease-id", *leaseID)
|
||||
}
|
||||
if blobContentDisposition != nil {
|
||||
req.Header.Set("x-ms-blob-content-disposition", *blobContentDisposition)
|
||||
}
|
||||
if encryptionKey != nil {
|
||||
req.Header.Set("x-ms-encryption-key", *encryptionKey)
|
||||
}
|
||||
if encryptionKeySha256 != nil {
|
||||
req.Header.Set("x-ms-encryption-key-sha256", *encryptionKeySha256)
|
||||
}
|
||||
if encryptionAlgorithm != EncryptionAlgorithmNone {
|
||||
req.Header.Set("x-ms-encryption-algorithm", string(encryptionAlgorithm))
|
||||
}
|
||||
if ifModifiedSince != nil {
|
||||
req.Header.Set("If-Modified-Since", (*ifModifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifUnmodifiedSince != nil {
|
||||
req.Header.Set("If-Unmodified-Since", (*ifUnmodifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifMatch != nil {
|
||||
req.Header.Set("If-Match", string(*ifMatch))
|
||||
}
|
||||
if ifNoneMatch != nil {
|
||||
req.Header.Set("If-None-Match", string(*ifNoneMatch))
|
||||
}
|
||||
req.Header.Set("x-ms-blob-content-length", strconv.FormatInt(blobContentLength, 10))
|
||||
if blobSequenceNumber != nil {
|
||||
req.Header.Set("x-ms-blob-sequence-number", strconv.FormatInt(*blobSequenceNumber, 10))
|
||||
}
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
req.Header.Set("x-ms-blob-type", "PageBlob")
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// createResponder handles the response to the Create request.
|
||||
func (client pageBlobClient) createResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK, http.StatusCreated)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(ioutil.Discard, resp.Response().Body)
|
||||
resp.Response().Body.Close()
|
||||
return &PageBlobCreateResponse{rawResponse: resp.Response()}, err
|
||||
}
|
||||
|
||||
// GetPageRanges the Get Page Ranges operation returns the list of valid page ranges for a page blob or snapshot of a
|
||||
// page blob
|
||||
//
|
||||
// snapshot is the snapshot parameter is an opaque DateTime value that, when present, specifies the blob snapshot to
|
||||
// retrieve. For more information on working with blob snapshots, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/creating-a-snapshot-of-a-blob">Creating
|
||||
// a Snapshot of a Blob.</a> timeout is the timeout parameter is expressed in seconds. For more information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> rangeParameter is return only the bytes of the blob in the specified
|
||||
// range. leaseID is if specified, the operation only succeeds if the resource's lease is active and matches this ID.
|
||||
// ifModifiedSince is specify this header value to operate only on a blob if it has been modified since the specified
|
||||
// date/time. ifUnmodifiedSince is specify this header value to operate only on a blob if it has not been modified
|
||||
// since the specified date/time. ifMatch is specify an ETag value to operate only on blobs with a matching value.
|
||||
// ifNoneMatch is specify an ETag value to operate only on blobs without a matching value. requestID is provides a
|
||||
// client-generated, opaque value with a 1 KB character limit that is recorded in the analytics logs when storage
|
||||
// analytics logging is enabled.
|
||||
func (client pageBlobClient) GetPageRanges(ctx context.Context, snapshot *string, timeout *int32, rangeParameter *string, leaseID *string, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (*PageList, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.getPageRangesPreparer(snapshot, timeout, rangeParameter, leaseID, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.getPageRangesResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*PageList), err
|
||||
}
|
||||
|
||||
// getPageRangesPreparer prepares the GetPageRanges request.
|
||||
func (client pageBlobClient) getPageRangesPreparer(snapshot *string, timeout *int32, rangeParameter *string, leaseID *string, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("GET", client.url, nil)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if snapshot != nil && len(*snapshot) > 0 {
|
||||
params.Set("snapshot", *snapshot)
|
||||
}
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
params.Set("comp", "pagelist")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
if rangeParameter != nil {
|
||||
req.Header.Set("x-ms-range", *rangeParameter)
|
||||
}
|
||||
if leaseID != nil {
|
||||
req.Header.Set("x-ms-lease-id", *leaseID)
|
||||
}
|
||||
if ifModifiedSince != nil {
|
||||
req.Header.Set("If-Modified-Since", (*ifModifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifUnmodifiedSince != nil {
|
||||
req.Header.Set("If-Unmodified-Since", (*ifUnmodifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifMatch != nil {
|
||||
req.Header.Set("If-Match", string(*ifMatch))
|
||||
}
|
||||
if ifNoneMatch != nil {
|
||||
req.Header.Set("If-None-Match", string(*ifNoneMatch))
|
||||
}
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// getPageRangesResponder handles the response to the GetPageRanges request.
|
||||
func (client pageBlobClient) getPageRangesResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &PageList{rawResponse: resp.Response()}
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
defer resp.Response().Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Response().Body)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if len(b) > 0 {
|
||||
b = removeBOM(b)
|
||||
err = xml.Unmarshal(b, result)
|
||||
if err != nil {
|
||||
return result, NewResponseError(err, resp.Response(), "failed to unmarshal response body")
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetPageRangesDiff the Get Page Ranges Diff operation returns the list of valid page ranges for a page blob that were
|
||||
// changed between target blob and previous snapshot.
|
||||
//
|
||||
// snapshot is the snapshot parameter is an opaque DateTime value that, when present, specifies the blob snapshot to
|
||||
// retrieve. For more information on working with blob snapshots, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/creating-a-snapshot-of-a-blob">Creating
|
||||
// a Snapshot of a Blob.</a> timeout is the timeout parameter is expressed in seconds. For more information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> prevsnapshot is optional in version 2015-07-08 and newer. The prevsnapshot
|
||||
// parameter is a DateTime value that specifies that the response will contain only pages that were changed between
|
||||
// target blob and previous snapshot. Changed pages include both updated and cleared pages. The target blob may be a
|
||||
// snapshot, as long as the snapshot specified by prevsnapshot is the older of the two. Note that incremental snapshots
|
||||
// are currently supported only for blobs created on or after January 1, 2016. rangeParameter is return only the bytes
|
||||
// of the blob in the specified range. leaseID is if specified, the operation only succeeds if the resource's lease is
|
||||
// active and matches this ID. ifModifiedSince is specify this header value to operate only on a blob if it has been
|
||||
// modified since the specified date/time. ifUnmodifiedSince is specify this header value to operate only on a blob if
|
||||
// it has not been modified since the specified date/time. ifMatch is specify an ETag value to operate only on blobs
|
||||
// with a matching value. ifNoneMatch is specify an ETag value to operate only on blobs without a matching value.
|
||||
// requestID is provides a client-generated, opaque value with a 1 KB character limit that is recorded in the analytics
|
||||
// logs when storage analytics logging is enabled.
|
||||
func (client pageBlobClient) GetPageRangesDiff(ctx context.Context, snapshot *string, timeout *int32, prevsnapshot *string, rangeParameter *string, leaseID *string, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (*PageList, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.getPageRangesDiffPreparer(snapshot, timeout, prevsnapshot, rangeParameter, leaseID, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.getPageRangesDiffResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*PageList), err
|
||||
}
|
||||
|
||||
// getPageRangesDiffPreparer prepares the GetPageRangesDiff request.
|
||||
func (client pageBlobClient) getPageRangesDiffPreparer(snapshot *string, timeout *int32, prevsnapshot *string, rangeParameter *string, leaseID *string, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("GET", client.url, nil)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if snapshot != nil && len(*snapshot) > 0 {
|
||||
params.Set("snapshot", *snapshot)
|
||||
}
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
if prevsnapshot != nil && len(*prevsnapshot) > 0 {
|
||||
params.Set("prevsnapshot", *prevsnapshot)
|
||||
}
|
||||
params.Set("comp", "pagelist")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
if rangeParameter != nil {
|
||||
req.Header.Set("x-ms-range", *rangeParameter)
|
||||
}
|
||||
if leaseID != nil {
|
||||
req.Header.Set("x-ms-lease-id", *leaseID)
|
||||
}
|
||||
if ifModifiedSince != nil {
|
||||
req.Header.Set("If-Modified-Since", (*ifModifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifUnmodifiedSince != nil {
|
||||
req.Header.Set("If-Unmodified-Since", (*ifUnmodifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifMatch != nil {
|
||||
req.Header.Set("If-Match", string(*ifMatch))
|
||||
}
|
||||
if ifNoneMatch != nil {
|
||||
req.Header.Set("If-None-Match", string(*ifNoneMatch))
|
||||
}
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// getPageRangesDiffResponder handles the response to the GetPageRangesDiff request.
|
||||
func (client pageBlobClient) getPageRangesDiffResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &PageList{rawResponse: resp.Response()}
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
defer resp.Response().Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Response().Body)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if len(b) > 0 {
|
||||
b = removeBOM(b)
|
||||
err = xml.Unmarshal(b, result)
|
||||
if err != nil {
|
||||
return result, NewResponseError(err, resp.Response(), "failed to unmarshal response body")
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Resize resize the Blob
|
||||
//
|
||||
// blobContentLength is this header specifies the maximum size for the page blob, up to 1 TB. The page blob size must
|
||||
// be aligned to a 512-byte boundary. timeout is the timeout parameter is expressed in seconds. For more information,
|
||||
// see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> leaseID is if specified, the operation only succeeds if the resource's
|
||||
// lease is active and matches this ID. encryptionKey is optional. Specifies the encryption key to use to encrypt the
|
||||
// data provided in the request. If not specified, encryption is performed with the root account encryption key. For
|
||||
// more information, see Encryption at Rest for Azure Storage Services. encryptionKeySha256 is the SHA-256 hash of the
|
||||
// provided encryption key. Must be provided if the x-ms-encryption-key header is provided. encryptionAlgorithm is the
|
||||
// algorithm used to produce the encryption key hash. Currently, the only accepted value is "AES256". Must be provided
|
||||
// if the x-ms-encryption-key header is provided. ifModifiedSince is specify this header value to operate only on a
|
||||
// blob if it has been modified since the specified date/time. ifUnmodifiedSince is specify this header value to
|
||||
// operate only on a blob if it has not been modified since the specified date/time. ifMatch is specify an ETag value
|
||||
// to operate only on blobs with a matching value. ifNoneMatch is specify an ETag value to operate only on blobs
|
||||
// without a matching value. requestID is provides a client-generated, opaque value with a 1 KB character limit that is
|
||||
// recorded in the analytics logs when storage analytics logging is enabled.
|
||||
func (client pageBlobClient) Resize(ctx context.Context, blobContentLength int64, timeout *int32, leaseID *string, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (*PageBlobResizeResponse, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.resizePreparer(blobContentLength, timeout, leaseID, encryptionKey, encryptionKeySha256, encryptionAlgorithm, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.resizeResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*PageBlobResizeResponse), err
|
||||
}
|
||||
|
||||
// resizePreparer prepares the Resize request.
|
||||
func (client pageBlobClient) resizePreparer(blobContentLength int64, timeout *int32, leaseID *string, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("PUT", client.url, nil)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
params.Set("comp", "properties")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
if leaseID != nil {
|
||||
req.Header.Set("x-ms-lease-id", *leaseID)
|
||||
}
|
||||
if encryptionKey != nil {
|
||||
req.Header.Set("x-ms-encryption-key", *encryptionKey)
|
||||
}
|
||||
if encryptionKeySha256 != nil {
|
||||
req.Header.Set("x-ms-encryption-key-sha256", *encryptionKeySha256)
|
||||
}
|
||||
if encryptionAlgorithm != EncryptionAlgorithmNone {
|
||||
req.Header.Set("x-ms-encryption-algorithm", string(encryptionAlgorithm))
|
||||
}
|
||||
if ifModifiedSince != nil {
|
||||
req.Header.Set("If-Modified-Since", (*ifModifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifUnmodifiedSince != nil {
|
||||
req.Header.Set("If-Unmodified-Since", (*ifUnmodifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifMatch != nil {
|
||||
req.Header.Set("If-Match", string(*ifMatch))
|
||||
}
|
||||
if ifNoneMatch != nil {
|
||||
req.Header.Set("If-None-Match", string(*ifNoneMatch))
|
||||
}
|
||||
req.Header.Set("x-ms-blob-content-length", strconv.FormatInt(blobContentLength, 10))
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// resizeResponder handles the response to the Resize request.
|
||||
func (client pageBlobClient) resizeResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(ioutil.Discard, resp.Response().Body)
|
||||
resp.Response().Body.Close()
|
||||
return &PageBlobResizeResponse{rawResponse: resp.Response()}, err
|
||||
}
|
||||
|
||||
// UpdateSequenceNumber update the sequence number of the blob
|
||||
//
|
||||
// sequenceNumberAction is required if the x-ms-blob-sequence-number header is set for the request. This property
|
||||
// applies to page blobs only. This property indicates how the service should modify the blob's sequence number timeout
|
||||
// is the timeout parameter is expressed in seconds. For more information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> leaseID is if specified, the operation only succeeds if the resource's
|
||||
// lease is active and matches this ID. ifModifiedSince is specify this header value to operate only on a blob if it
|
||||
// has been modified since the specified date/time. ifUnmodifiedSince is specify this header value to operate only on a
|
||||
// blob if it has not been modified since the specified date/time. ifMatch is specify an ETag value to operate only on
|
||||
// blobs with a matching value. ifNoneMatch is specify an ETag value to operate only on blobs without a matching value.
|
||||
// blobSequenceNumber is set for page blobs only. The sequence number is a user-controlled value that you can use to
|
||||
// track requests. The value of the sequence number must be between 0 and 2^63 - 1. requestID is provides a
|
||||
// client-generated, opaque value with a 1 KB character limit that is recorded in the analytics logs when storage
|
||||
// analytics logging is enabled.
|
||||
func (client pageBlobClient) UpdateSequenceNumber(ctx context.Context, sequenceNumberAction SequenceNumberActionType, timeout *int32, leaseID *string, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, blobSequenceNumber *int64, requestID *string) (*PageBlobUpdateSequenceNumberResponse, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.updateSequenceNumberPreparer(sequenceNumberAction, timeout, leaseID, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, blobSequenceNumber, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.updateSequenceNumberResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*PageBlobUpdateSequenceNumberResponse), err
|
||||
}
|
||||
|
||||
// updateSequenceNumberPreparer prepares the UpdateSequenceNumber request.
|
||||
func (client pageBlobClient) updateSequenceNumberPreparer(sequenceNumberAction SequenceNumberActionType, timeout *int32, leaseID *string, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, blobSequenceNumber *int64, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("PUT", client.url, nil)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
params.Set("comp", "properties")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
if leaseID != nil {
|
||||
req.Header.Set("x-ms-lease-id", *leaseID)
|
||||
}
|
||||
if ifModifiedSince != nil {
|
||||
req.Header.Set("If-Modified-Since", (*ifModifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifUnmodifiedSince != nil {
|
||||
req.Header.Set("If-Unmodified-Since", (*ifUnmodifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifMatch != nil {
|
||||
req.Header.Set("If-Match", string(*ifMatch))
|
||||
}
|
||||
if ifNoneMatch != nil {
|
||||
req.Header.Set("If-None-Match", string(*ifNoneMatch))
|
||||
}
|
||||
req.Header.Set("x-ms-sequence-number-action", string(sequenceNumberAction))
|
||||
if blobSequenceNumber != nil {
|
||||
req.Header.Set("x-ms-blob-sequence-number", strconv.FormatInt(*blobSequenceNumber, 10))
|
||||
}
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// updateSequenceNumberResponder handles the response to the UpdateSequenceNumber request.
|
||||
func (client pageBlobClient) updateSequenceNumberResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(ioutil.Discard, resp.Response().Body)
|
||||
resp.Response().Body.Close()
|
||||
return &PageBlobUpdateSequenceNumberResponse{rawResponse: resp.Response()}, err
|
||||
}
|
||||
|
||||
// UploadPages the Upload Pages operation writes a range of pages to a page blob
|
||||
//
|
||||
// body is initial data body will be closed upon successful return. Callers should ensure closure when receiving an
|
||||
// error.contentLength is the length of the request. transactionalContentMD5 is specify the transactional md5 for the
|
||||
// body, to be validated by the service. transactionalContentCrc64 is specify the transactional crc64 for the body, to
|
||||
// be validated by the service. timeout is the timeout parameter is expressed in seconds. For more information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> rangeParameter is return only the bytes of the blob in the specified
|
||||
// range. leaseID is if specified, the operation only succeeds if the resource's lease is active and matches this ID.
|
||||
// encryptionKey is optional. Specifies the encryption key to use to encrypt the data provided in the request. If not
|
||||
// specified, encryption is performed with the root account encryption key. For more information, see Encryption at
|
||||
// Rest for Azure Storage Services. encryptionKeySha256 is the SHA-256 hash of the provided encryption key. Must be
|
||||
// provided if the x-ms-encryption-key header is provided. encryptionAlgorithm is the algorithm used to produce the
|
||||
// encryption key hash. Currently, the only accepted value is "AES256". Must be provided if the x-ms-encryption-key
|
||||
// header is provided. ifSequenceNumberLessThanOrEqualTo is specify this header value to operate only on a blob if it
|
||||
// has a sequence number less than or equal to the specified. ifSequenceNumberLessThan is specify this header value to
|
||||
// operate only on a blob if it has a sequence number less than the specified. ifSequenceNumberEqualTo is specify this
|
||||
// header value to operate only on a blob if it has the specified sequence number. ifModifiedSince is specify this
|
||||
// header value to operate only on a blob if it has been modified since the specified date/time. ifUnmodifiedSince is
|
||||
// specify this header value to operate only on a blob if it has not been modified since the specified date/time.
|
||||
// ifMatch is specify an ETag value to operate only on blobs with a matching value. ifNoneMatch is specify an ETag
|
||||
// value to operate only on blobs without a matching value. requestID is provides a client-generated, opaque value with
|
||||
// a 1 KB character limit that is recorded in the analytics logs when storage analytics logging is enabled.
|
||||
func (client pageBlobClient) UploadPages(ctx context.Context, body io.ReadSeeker, contentLength int64, transactionalContentMD5 []byte, transactionalContentCrc64 []byte, timeout *int32, rangeParameter *string, leaseID *string, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, ifSequenceNumberLessThanOrEqualTo *int64, ifSequenceNumberLessThan *int64, ifSequenceNumberEqualTo *int64, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (*PageBlobUploadPagesResponse, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: body,
|
||||
constraints: []constraint{{target: "body", name: null, rule: true, chain: nil}}},
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.uploadPagesPreparer(body, contentLength, transactionalContentMD5, transactionalContentCrc64, timeout, rangeParameter, leaseID, encryptionKey, encryptionKeySha256, encryptionAlgorithm, ifSequenceNumberLessThanOrEqualTo, ifSequenceNumberLessThan, ifSequenceNumberEqualTo, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.uploadPagesResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*PageBlobUploadPagesResponse), err
|
||||
}
|
||||
|
||||
// uploadPagesPreparer prepares the UploadPages request.
|
||||
func (client pageBlobClient) uploadPagesPreparer(body io.ReadSeeker, contentLength int64, transactionalContentMD5 []byte, transactionalContentCrc64 []byte, timeout *int32, rangeParameter *string, leaseID *string, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, ifSequenceNumberLessThanOrEqualTo *int64, ifSequenceNumberLessThan *int64, ifSequenceNumberEqualTo *int64, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("PUT", client.url, body)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
params.Set("comp", "page")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
req.Header.Set("Content-Length", strconv.FormatInt(contentLength, 10))
|
||||
if transactionalContentMD5 != nil {
|
||||
req.Header.Set("Content-MD5", base64.StdEncoding.EncodeToString(transactionalContentMD5))
|
||||
}
|
||||
if transactionalContentCrc64 != nil {
|
||||
req.Header.Set("x-ms-content-crc64", base64.StdEncoding.EncodeToString(transactionalContentCrc64))
|
||||
}
|
||||
if rangeParameter != nil {
|
||||
req.Header.Set("x-ms-range", *rangeParameter)
|
||||
}
|
||||
if leaseID != nil {
|
||||
req.Header.Set("x-ms-lease-id", *leaseID)
|
||||
}
|
||||
if encryptionKey != nil {
|
||||
req.Header.Set("x-ms-encryption-key", *encryptionKey)
|
||||
}
|
||||
if encryptionKeySha256 != nil {
|
||||
req.Header.Set("x-ms-encryption-key-sha256", *encryptionKeySha256)
|
||||
}
|
||||
if encryptionAlgorithm != EncryptionAlgorithmNone {
|
||||
req.Header.Set("x-ms-encryption-algorithm", string(encryptionAlgorithm))
|
||||
}
|
||||
if ifSequenceNumberLessThanOrEqualTo != nil {
|
||||
req.Header.Set("x-ms-if-sequence-number-le", strconv.FormatInt(*ifSequenceNumberLessThanOrEqualTo, 10))
|
||||
}
|
||||
if ifSequenceNumberLessThan != nil {
|
||||
req.Header.Set("x-ms-if-sequence-number-lt", strconv.FormatInt(*ifSequenceNumberLessThan, 10))
|
||||
}
|
||||
if ifSequenceNumberEqualTo != nil {
|
||||
req.Header.Set("x-ms-if-sequence-number-eq", strconv.FormatInt(*ifSequenceNumberEqualTo, 10))
|
||||
}
|
||||
if ifModifiedSince != nil {
|
||||
req.Header.Set("If-Modified-Since", (*ifModifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifUnmodifiedSince != nil {
|
||||
req.Header.Set("If-Unmodified-Since", (*ifUnmodifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifMatch != nil {
|
||||
req.Header.Set("If-Match", string(*ifMatch))
|
||||
}
|
||||
if ifNoneMatch != nil {
|
||||
req.Header.Set("If-None-Match", string(*ifNoneMatch))
|
||||
}
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
req.Header.Set("x-ms-page-write", "update")
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// uploadPagesResponder handles the response to the UploadPages request.
|
||||
func (client pageBlobClient) uploadPagesResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK, http.StatusCreated)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(ioutil.Discard, resp.Response().Body)
|
||||
resp.Response().Body.Close()
|
||||
return &PageBlobUploadPagesResponse{rawResponse: resp.Response()}, err
|
||||
}
|
||||
|
||||
// UploadPagesFromURL the Upload Pages operation writes a range of pages to a page blob where the contents are read
|
||||
// from a URL
|
||||
//
|
||||
// sourceURL is specify a URL to the copy source. sourceRange is bytes of source data in the specified range. The
|
||||
// length of this range should match the ContentLength header and x-ms-range/Range destination range header.
|
||||
// contentLength is the length of the request. rangeParameter is the range of bytes to which the source range would be
|
||||
// written. The range should be 512 aligned and range-end is required. sourceContentMD5 is specify the md5 calculated
|
||||
// for the range of bytes that must be read from the copy source. sourceContentcrc64 is specify the crc64 calculated
|
||||
// for the range of bytes that must be read from the copy source. timeout is the timeout parameter is expressed in
|
||||
// seconds. For more information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> encryptionKey is optional. Specifies the encryption key to use to encrypt
|
||||
// the data provided in the request. If not specified, encryption is performed with the root account encryption key.
|
||||
// For more information, see Encryption at Rest for Azure Storage Services. encryptionKeySha256 is the SHA-256 hash of
|
||||
// the provided encryption key. Must be provided if the x-ms-encryption-key header is provided. encryptionAlgorithm is
|
||||
// the algorithm used to produce the encryption key hash. Currently, the only accepted value is "AES256". Must be
|
||||
// provided if the x-ms-encryption-key header is provided. leaseID is if specified, the operation only succeeds if the
|
||||
// resource's lease is active and matches this ID. ifSequenceNumberLessThanOrEqualTo is specify this header value to
|
||||
// operate only on a blob if it has a sequence number less than or equal to the specified. ifSequenceNumberLessThan is
|
||||
// specify this header value to operate only on a blob if it has a sequence number less than the specified.
|
||||
// ifSequenceNumberEqualTo is specify this header value to operate only on a blob if it has the specified sequence
|
||||
// number. ifModifiedSince is specify this header value to operate only on a blob if it has been modified since the
|
||||
// specified date/time. ifUnmodifiedSince is specify this header value to operate only on a blob if it has not been
|
||||
// modified since the specified date/time. ifMatch is specify an ETag value to operate only on blobs with a matching
|
||||
// value. ifNoneMatch is specify an ETag value to operate only on blobs without a matching value. sourceIfModifiedSince
|
||||
// is specify this header value to operate only on a blob if it has been modified since the specified date/time.
|
||||
// sourceIfUnmodifiedSince is specify this header value to operate only on a blob if it has not been modified since the
|
||||
// specified date/time. sourceIfMatch is specify an ETag value to operate only on blobs with a matching value.
|
||||
// sourceIfNoneMatch is specify an ETag value to operate only on blobs without a matching value. requestID is provides
|
||||
// a client-generated, opaque value with a 1 KB character limit that is recorded in the analytics logs when storage
|
||||
// analytics logging is enabled.
|
||||
func (client pageBlobClient) UploadPagesFromURL(ctx context.Context, sourceURL string, sourceRange string, contentLength int64, rangeParameter string, sourceContentMD5 []byte, sourceContentcrc64 []byte, timeout *int32, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, leaseID *string, ifSequenceNumberLessThanOrEqualTo *int64, ifSequenceNumberLessThan *int64, ifSequenceNumberEqualTo *int64, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, sourceIfModifiedSince *time.Time, sourceIfUnmodifiedSince *time.Time, sourceIfMatch *ETag, sourceIfNoneMatch *ETag, requestID *string) (*PageBlobUploadPagesFromURLResponse, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.uploadPagesFromURLPreparer(sourceURL, sourceRange, contentLength, rangeParameter, sourceContentMD5, sourceContentcrc64, timeout, encryptionKey, encryptionKeySha256, encryptionAlgorithm, leaseID, ifSequenceNumberLessThanOrEqualTo, ifSequenceNumberLessThan, ifSequenceNumberEqualTo, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, sourceIfModifiedSince, sourceIfUnmodifiedSince, sourceIfMatch, sourceIfNoneMatch, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.uploadPagesFromURLResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*PageBlobUploadPagesFromURLResponse), err
|
||||
}
|
||||
|
||||
// uploadPagesFromURLPreparer prepares the UploadPagesFromURL request.
|
||||
func (client pageBlobClient) uploadPagesFromURLPreparer(sourceURL string, sourceRange string, contentLength int64, rangeParameter string, sourceContentMD5 []byte, sourceContentcrc64 []byte, timeout *int32, encryptionKey *string, encryptionKeySha256 *string, encryptionAlgorithm EncryptionAlgorithmType, leaseID *string, ifSequenceNumberLessThanOrEqualTo *int64, ifSequenceNumberLessThan *int64, ifSequenceNumberEqualTo *int64, ifModifiedSince *time.Time, ifUnmodifiedSince *time.Time, ifMatch *ETag, ifNoneMatch *ETag, sourceIfModifiedSince *time.Time, sourceIfUnmodifiedSince *time.Time, sourceIfMatch *ETag, sourceIfNoneMatch *ETag, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("PUT", client.url, nil)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
params.Set("comp", "page")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
req.Header.Set("x-ms-copy-source", sourceURL)
|
||||
req.Header.Set("x-ms-source-range", sourceRange)
|
||||
if sourceContentMD5 != nil {
|
||||
req.Header.Set("x-ms-source-content-md5", base64.StdEncoding.EncodeToString(sourceContentMD5))
|
||||
}
|
||||
if sourceContentcrc64 != nil {
|
||||
req.Header.Set("x-ms-source-content-crc64", base64.StdEncoding.EncodeToString(sourceContentcrc64))
|
||||
}
|
||||
req.Header.Set("Content-Length", strconv.FormatInt(contentLength, 10))
|
||||
req.Header.Set("x-ms-range", rangeParameter)
|
||||
if encryptionKey != nil {
|
||||
req.Header.Set("x-ms-encryption-key", *encryptionKey)
|
||||
}
|
||||
if encryptionKeySha256 != nil {
|
||||
req.Header.Set("x-ms-encryption-key-sha256", *encryptionKeySha256)
|
||||
}
|
||||
if encryptionAlgorithm != EncryptionAlgorithmNone {
|
||||
req.Header.Set("x-ms-encryption-algorithm", string(encryptionAlgorithm))
|
||||
}
|
||||
if leaseID != nil {
|
||||
req.Header.Set("x-ms-lease-id", *leaseID)
|
||||
}
|
||||
if ifSequenceNumberLessThanOrEqualTo != nil {
|
||||
req.Header.Set("x-ms-if-sequence-number-le", strconv.FormatInt(*ifSequenceNumberLessThanOrEqualTo, 10))
|
||||
}
|
||||
if ifSequenceNumberLessThan != nil {
|
||||
req.Header.Set("x-ms-if-sequence-number-lt", strconv.FormatInt(*ifSequenceNumberLessThan, 10))
|
||||
}
|
||||
if ifSequenceNumberEqualTo != nil {
|
||||
req.Header.Set("x-ms-if-sequence-number-eq", strconv.FormatInt(*ifSequenceNumberEqualTo, 10))
|
||||
}
|
||||
if ifModifiedSince != nil {
|
||||
req.Header.Set("If-Modified-Since", (*ifModifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifUnmodifiedSince != nil {
|
||||
req.Header.Set("If-Unmodified-Since", (*ifUnmodifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if ifMatch != nil {
|
||||
req.Header.Set("If-Match", string(*ifMatch))
|
||||
}
|
||||
if ifNoneMatch != nil {
|
||||
req.Header.Set("If-None-Match", string(*ifNoneMatch))
|
||||
}
|
||||
if sourceIfModifiedSince != nil {
|
||||
req.Header.Set("x-ms-source-if-modified-since", (*sourceIfModifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if sourceIfUnmodifiedSince != nil {
|
||||
req.Header.Set("x-ms-source-if-unmodified-since", (*sourceIfUnmodifiedSince).In(gmt).Format(time.RFC1123))
|
||||
}
|
||||
if sourceIfMatch != nil {
|
||||
req.Header.Set("x-ms-source-if-match", string(*sourceIfMatch))
|
||||
}
|
||||
if sourceIfNoneMatch != nil {
|
||||
req.Header.Set("x-ms-source-if-none-match", string(*sourceIfNoneMatch))
|
||||
}
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
req.Header.Set("x-ms-page-write", "update")
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// uploadPagesFromURLResponder handles the response to the UploadPagesFromURL request.
|
||||
func (client pageBlobClient) uploadPagesFromURLResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK, http.StatusCreated)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(ioutil.Discard, resp.Response().Body)
|
||||
resp.Response().Body.Close()
|
||||
return &PageBlobUploadPagesFromURLResponse{rawResponse: resp.Response()}, err
|
||||
}
|
74
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_responder_policy.go
generated
vendored
Normal file
74
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_responder_policy.go
generated
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
package azblob
|
||||
|
||||
// Code generated by Microsoft (R) AutoRest Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
type responder func(resp pipeline.Response) (result pipeline.Response, err error)
|
||||
|
||||
// ResponderPolicyFactory is a Factory capable of creating a responder pipeline.
|
||||
type responderPolicyFactory struct {
|
||||
responder responder
|
||||
}
|
||||
|
||||
// New creates a responder policy factory.
|
||||
func (arpf responderPolicyFactory) New(next pipeline.Policy, po *pipeline.PolicyOptions) pipeline.Policy {
|
||||
return responderPolicy{next: next, responder: arpf.responder}
|
||||
}
|
||||
|
||||
type responderPolicy struct {
|
||||
next pipeline.Policy
|
||||
responder responder
|
||||
}
|
||||
|
||||
// Do sends the request to the service and validates/deserializes the HTTP response.
|
||||
func (arp responderPolicy) Do(ctx context.Context, request pipeline.Request) (pipeline.Response, error) {
|
||||
resp, err := arp.next.Do(ctx, request)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
return arp.responder(resp)
|
||||
}
|
||||
|
||||
// validateResponse checks an HTTP response's status code against a legal set of codes.
|
||||
// If the response code is not legal, then validateResponse reads all of the response's body
|
||||
// (containing error information) and returns a response error.
|
||||
func validateResponse(resp pipeline.Response, successStatusCodes ...int) error {
|
||||
if resp == nil {
|
||||
return NewResponseError(nil, nil, "nil response")
|
||||
}
|
||||
responseCode := resp.Response().StatusCode
|
||||
for _, i := range successStatusCodes {
|
||||
if i == responseCode {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// only close the body in the failure case. in the
|
||||
// success case responders will close the body as required.
|
||||
defer resp.Response().Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Response().Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// the service code, description and details will be populated during unmarshalling
|
||||
responseError := NewResponseError(nil, resp.Response(), resp.Response().Status)
|
||||
if len(b) > 0 {
|
||||
if err = xml.Unmarshal(b, &responseError); err != nil {
|
||||
return NewResponseError(err, resp.Response(), "failed to unmarshal response body")
|
||||
}
|
||||
}
|
||||
return responseError
|
||||
}
|
||||
|
||||
// removes any BOM from the byte slice
|
||||
func removeBOM(b []byte) []byte {
|
||||
// UTF8
|
||||
return bytes.TrimPrefix(b, []byte("\xef\xbb\xbf"))
|
||||
}
|
95
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_response_error.go
generated
vendored
Normal file
95
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_response_error.go
generated
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
package azblob
|
||||
|
||||
// Code generated by Microsoft (R) AutoRest Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// if you want to provide custom error handling set this variable to your constructor function
|
||||
var responseErrorFactory func(cause error, response *http.Response, description string) error
|
||||
|
||||
// ResponseError identifies a responder-generated network or response parsing error.
|
||||
type ResponseError interface {
|
||||
// Error exposes the Error(), Temporary() and Timeout() methods.
|
||||
net.Error // Includes the Go error interface
|
||||
// Response returns the HTTP response. You may examine this but you should not modify it.
|
||||
Response() *http.Response
|
||||
}
|
||||
|
||||
// NewResponseError creates an error object that implements the error interface.
|
||||
func NewResponseError(cause error, response *http.Response, description string) error {
|
||||
if responseErrorFactory != nil {
|
||||
return responseErrorFactory(cause, response, description)
|
||||
}
|
||||
return &responseError{
|
||||
ErrorNode: pipeline.ErrorNode{}.Initialize(cause, 3),
|
||||
response: response,
|
||||
description: description,
|
||||
}
|
||||
}
|
||||
|
||||
// responseError is the internal struct that implements the public ResponseError interface.
|
||||
type responseError struct {
|
||||
pipeline.ErrorNode // This is embedded so that responseError "inherits" Error, Temporary, Timeout, and Cause
|
||||
response *http.Response
|
||||
description string
|
||||
}
|
||||
|
||||
// Error implements the error interface's Error method to return a string representation of the error.
|
||||
func (e *responseError) Error() string {
|
||||
b := &bytes.Buffer{}
|
||||
fmt.Fprintf(b, "===== RESPONSE ERROR (Code=%v) =====\n", e.response.StatusCode)
|
||||
fmt.Fprintf(b, "Status=%s, Description: %s\n", e.response.Status, e.description)
|
||||
s := b.String()
|
||||
return e.ErrorNode.Error(s)
|
||||
}
|
||||
|
||||
// Response implements the ResponseError interface's method to return the HTTP response.
|
||||
func (e *responseError) Response() *http.Response {
|
||||
return e.response
|
||||
}
|
||||
|
||||
// RFC7807 PROBLEM ------------------------------------------------------------------------------------
|
||||
// RFC7807Problem ... This type can be publicly embedded in another type that wants to add additional members.
|
||||
/*type RFC7807Problem struct {
|
||||
// Mandatory: A (relative) URI reference identifying the problem type (it MAY refer to human-readable documentation).
|
||||
typeURI string // Should default to "about:blank"
|
||||
// Optional: Short, human-readable summary (maybe localized).
|
||||
title string
|
||||
// Optional: HTTP status code generated by the origin server
|
||||
status int
|
||||
// Optional: Human-readable explanation for this problem occurance.
|
||||
// Should help client correct the problem. Clients should NOT parse this string.
|
||||
detail string
|
||||
// Optional: A (relative) URI identifying this specific problem occurence (it may or may not be dereferenced).
|
||||
instance string
|
||||
}
|
||||
// NewRFC7807Problem ...
|
||||
func NewRFC7807Problem(typeURI string, status int, titleFormat string, a ...interface{}) error {
|
||||
return &RFC7807Problem{
|
||||
typeURI: typeURI,
|
||||
status: status,
|
||||
title: fmt.Sprintf(titleFormat, a...),
|
||||
}
|
||||
}
|
||||
// Error returns the error information as a string.
|
||||
func (e *RFC7807Problem) Error() string {
|
||||
return e.title
|
||||
}
|
||||
// TypeURI ...
|
||||
func (e *RFC7807Problem) TypeURI() string {
|
||||
if e.typeURI == "" {
|
||||
e.typeURI = "about:blank"
|
||||
}
|
||||
return e.typeURI
|
||||
}
|
||||
// Members ...
|
||||
func (e *RFC7807Problem) Members() (status int, title, detail, instance string) {
|
||||
return e.status, e.title, e.detail, e.instance
|
||||
}*/
|
526
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_service.go
generated
vendored
Normal file
526
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_service.go
generated
vendored
Normal file
|
@ -0,0 +1,526 @@
|
|||
package azblob
|
||||
|
||||
// Code generated by Microsoft (R) AutoRest Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// serviceClient is the client for the Service methods of the Azblob service.
|
||||
type serviceClient struct {
|
||||
managementClient
|
||||
}
|
||||
|
||||
// newServiceClient creates an instance of the serviceClient client.
|
||||
func newServiceClient(url url.URL, p pipeline.Pipeline) serviceClient {
|
||||
return serviceClient{newManagementClient(url, p)}
|
||||
}
|
||||
|
||||
// GetAccountInfo returns the sku name and account kind
|
||||
func (client serviceClient) GetAccountInfo(ctx context.Context) (*ServiceGetAccountInfoResponse, error) {
|
||||
req, err := client.getAccountInfoPreparer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.getAccountInfoResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*ServiceGetAccountInfoResponse), err
|
||||
}
|
||||
|
||||
// getAccountInfoPreparer prepares the GetAccountInfo request.
|
||||
func (client serviceClient) getAccountInfoPreparer() (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("GET", client.url, nil)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
params.Set("restype", "account")
|
||||
params.Set("comp", "properties")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// getAccountInfoResponder handles the response to the GetAccountInfo request.
|
||||
func (client serviceClient) getAccountInfoResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(ioutil.Discard, resp.Response().Body)
|
||||
resp.Response().Body.Close()
|
||||
return &ServiceGetAccountInfoResponse{rawResponse: resp.Response()}, err
|
||||
}
|
||||
|
||||
// GetProperties gets the properties of a storage account's Blob service, including properties for Storage Analytics
|
||||
// and CORS (Cross-Origin Resource Sharing) rules.
|
||||
//
|
||||
// timeout is the timeout parameter is expressed in seconds. For more information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> requestID is provides a client-generated, opaque value with a 1 KB
|
||||
// character limit that is recorded in the analytics logs when storage analytics logging is enabled.
|
||||
func (client serviceClient) GetProperties(ctx context.Context, timeout *int32, requestID *string) (*StorageServiceProperties, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.getPropertiesPreparer(timeout, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.getPropertiesResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*StorageServiceProperties), err
|
||||
}
|
||||
|
||||
// getPropertiesPreparer prepares the GetProperties request.
|
||||
func (client serviceClient) getPropertiesPreparer(timeout *int32, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("GET", client.url, nil)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
params.Set("restype", "service")
|
||||
params.Set("comp", "properties")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// getPropertiesResponder handles the response to the GetProperties request.
|
||||
func (client serviceClient) getPropertiesResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &StorageServiceProperties{rawResponse: resp.Response()}
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
defer resp.Response().Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Response().Body)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if len(b) > 0 {
|
||||
b = removeBOM(b)
|
||||
err = xml.Unmarshal(b, result)
|
||||
if err != nil {
|
||||
return result, NewResponseError(err, resp.Response(), "failed to unmarshal response body")
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetStatistics retrieves statistics related to replication for the Blob service. It is only available on the
|
||||
// secondary location endpoint when read-access geo-redundant replication is enabled for the storage account.
|
||||
//
|
||||
// timeout is the timeout parameter is expressed in seconds. For more information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> requestID is provides a client-generated, opaque value with a 1 KB
|
||||
// character limit that is recorded in the analytics logs when storage analytics logging is enabled.
|
||||
func (client serviceClient) GetStatistics(ctx context.Context, timeout *int32, requestID *string) (*StorageServiceStats, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.getStatisticsPreparer(timeout, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.getStatisticsResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*StorageServiceStats), err
|
||||
}
|
||||
|
||||
// getStatisticsPreparer prepares the GetStatistics request.
|
||||
func (client serviceClient) getStatisticsPreparer(timeout *int32, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("GET", client.url, nil)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
params.Set("restype", "service")
|
||||
params.Set("comp", "stats")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// getStatisticsResponder handles the response to the GetStatistics request.
|
||||
func (client serviceClient) getStatisticsResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &StorageServiceStats{rawResponse: resp.Response()}
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
defer resp.Response().Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Response().Body)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if len(b) > 0 {
|
||||
b = removeBOM(b)
|
||||
err = xml.Unmarshal(b, result)
|
||||
if err != nil {
|
||||
return result, NewResponseError(err, resp.Response(), "failed to unmarshal response body")
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetUserDelegationKey retrieves a user delegation key for the Blob service. This is only a valid operation when using
|
||||
// bearer token authentication.
|
||||
//
|
||||
// timeout is the timeout parameter is expressed in seconds. For more information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> requestID is provides a client-generated, opaque value with a 1 KB
|
||||
// character limit that is recorded in the analytics logs when storage analytics logging is enabled.
|
||||
func (client serviceClient) GetUserDelegationKey(ctx context.Context, keyInfo KeyInfo, timeout *int32, requestID *string) (*UserDelegationKey, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.getUserDelegationKeyPreparer(keyInfo, timeout, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.getUserDelegationKeyResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*UserDelegationKey), err
|
||||
}
|
||||
|
||||
// getUserDelegationKeyPreparer prepares the GetUserDelegationKey request.
|
||||
func (client serviceClient) getUserDelegationKeyPreparer(keyInfo KeyInfo, timeout *int32, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("POST", client.url, nil)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
params.Set("restype", "service")
|
||||
params.Set("comp", "userdelegationkey")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
b, err := xml.Marshal(keyInfo)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to marshal request body")
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/xml")
|
||||
err = req.SetBody(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to set request body")
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// getUserDelegationKeyResponder handles the response to the GetUserDelegationKey request.
|
||||
func (client serviceClient) getUserDelegationKeyResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &UserDelegationKey{rawResponse: resp.Response()}
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
defer resp.Response().Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Response().Body)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if len(b) > 0 {
|
||||
b = removeBOM(b)
|
||||
err = xml.Unmarshal(b, result)
|
||||
if err != nil {
|
||||
return result, NewResponseError(err, resp.Response(), "failed to unmarshal response body")
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ListContainersSegment the List Containers Segment operation returns a list of the containers under the specified
|
||||
// account
|
||||
//
|
||||
// prefix is filters the results to return only containers whose name begins with the specified prefix. marker is a
|
||||
// string value that identifies the portion of the list of containers to be returned with the next listing operation.
|
||||
// The operation returns the NextMarker value within the response body if the listing operation did not return all
|
||||
// containers remaining to be listed with the current page. The NextMarker value can be used as the value for the
|
||||
// marker parameter in a subsequent call to request the next page of list items. The marker value is opaque to the
|
||||
// client. maxresults is specifies the maximum number of containers to return. If the request does not specify
|
||||
// maxresults, or specifies a value greater than 5000, the server will return up to 5000 items. Note that if the
|
||||
// listing operation crosses a partition boundary, then the service will return a continuation token for retrieving the
|
||||
// remainder of the results. For this reason, it is possible that the service will return fewer results than specified
|
||||
// by maxresults, or than the default of 5000. include is include this parameter to specify that the container's
|
||||
// metadata be returned as part of the response body. timeout is the timeout parameter is expressed in seconds. For
|
||||
// more information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> requestID is provides a client-generated, opaque value with a 1 KB
|
||||
// character limit that is recorded in the analytics logs when storage analytics logging is enabled.
|
||||
func (client serviceClient) ListContainersSegment(ctx context.Context, prefix *string, marker *string, maxresults *int32, include ListContainersIncludeType, timeout *int32, requestID *string) (*ListContainersSegmentResponse, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: maxresults,
|
||||
constraints: []constraint{{target: "maxresults", name: null, rule: false,
|
||||
chain: []constraint{{target: "maxresults", name: inclusiveMinimum, rule: 1, chain: nil}}}}},
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.listContainersSegmentPreparer(prefix, marker, maxresults, include, timeout, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.listContainersSegmentResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*ListContainersSegmentResponse), err
|
||||
}
|
||||
|
||||
// listContainersSegmentPreparer prepares the ListContainersSegment request.
|
||||
func (client serviceClient) listContainersSegmentPreparer(prefix *string, marker *string, maxresults *int32, include ListContainersIncludeType, timeout *int32, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("GET", client.url, nil)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if prefix != nil && len(*prefix) > 0 {
|
||||
params.Set("prefix", *prefix)
|
||||
}
|
||||
if marker != nil && len(*marker) > 0 {
|
||||
params.Set("marker", *marker)
|
||||
}
|
||||
if maxresults != nil {
|
||||
params.Set("maxresults", strconv.FormatInt(int64(*maxresults), 10))
|
||||
}
|
||||
if include != ListContainersIncludeNone {
|
||||
params.Set("include", string(include))
|
||||
}
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
params.Set("comp", "list")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// listContainersSegmentResponder handles the response to the ListContainersSegment request.
|
||||
func (client serviceClient) listContainersSegmentResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &ListContainersSegmentResponse{rawResponse: resp.Response()}
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
defer resp.Response().Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Response().Body)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if len(b) > 0 {
|
||||
b = removeBOM(b)
|
||||
err = xml.Unmarshal(b, result)
|
||||
if err != nil {
|
||||
return result, NewResponseError(err, resp.Response(), "failed to unmarshal response body")
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SetProperties sets properties for a storage account's Blob service endpoint, including properties for Storage
|
||||
// Analytics and CORS (Cross-Origin Resource Sharing) rules
|
||||
//
|
||||
// storageServiceProperties is the StorageService properties. timeout is the timeout parameter is expressed in seconds.
|
||||
// For more information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> requestID is provides a client-generated, opaque value with a 1 KB
|
||||
// character limit that is recorded in the analytics logs when storage analytics logging is enabled.
|
||||
func (client serviceClient) SetProperties(ctx context.Context, storageServiceProperties StorageServiceProperties, timeout *int32, requestID *string) (*ServiceSetPropertiesResponse, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: storageServiceProperties,
|
||||
constraints: []constraint{{target: "storageServiceProperties.Logging", name: null, rule: false,
|
||||
chain: []constraint{{target: "storageServiceProperties.Logging.RetentionPolicy", name: null, rule: true,
|
||||
chain: []constraint{{target: "storageServiceProperties.Logging.RetentionPolicy.Days", name: null, rule: false,
|
||||
chain: []constraint{{target: "storageServiceProperties.Logging.RetentionPolicy.Days", name: inclusiveMinimum, rule: 1, chain: nil}}},
|
||||
}},
|
||||
}},
|
||||
{target: "storageServiceProperties.HourMetrics", name: null, rule: false,
|
||||
chain: []constraint{{target: "storageServiceProperties.HourMetrics.RetentionPolicy", name: null, rule: false,
|
||||
chain: []constraint{{target: "storageServiceProperties.HourMetrics.RetentionPolicy.Days", name: null, rule: false,
|
||||
chain: []constraint{{target: "storageServiceProperties.HourMetrics.RetentionPolicy.Days", name: inclusiveMinimum, rule: 1, chain: nil}}},
|
||||
}},
|
||||
}},
|
||||
{target: "storageServiceProperties.MinuteMetrics", name: null, rule: false,
|
||||
chain: []constraint{{target: "storageServiceProperties.MinuteMetrics.RetentionPolicy", name: null, rule: false,
|
||||
chain: []constraint{{target: "storageServiceProperties.MinuteMetrics.RetentionPolicy.Days", name: null, rule: false,
|
||||
chain: []constraint{{target: "storageServiceProperties.MinuteMetrics.RetentionPolicy.Days", name: inclusiveMinimum, rule: 1, chain: nil}}},
|
||||
}},
|
||||
}},
|
||||
{target: "storageServiceProperties.DeleteRetentionPolicy", name: null, rule: false,
|
||||
chain: []constraint{{target: "storageServiceProperties.DeleteRetentionPolicy.Days", name: null, rule: false,
|
||||
chain: []constraint{{target: "storageServiceProperties.DeleteRetentionPolicy.Days", name: inclusiveMinimum, rule: 1, chain: nil}}},
|
||||
}}}},
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.setPropertiesPreparer(storageServiceProperties, timeout, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.setPropertiesResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*ServiceSetPropertiesResponse), err
|
||||
}
|
||||
|
||||
// setPropertiesPreparer prepares the SetProperties request.
|
||||
func (client serviceClient) setPropertiesPreparer(storageServiceProperties StorageServiceProperties, timeout *int32, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("PUT", client.url, nil)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
params.Set("restype", "service")
|
||||
params.Set("comp", "properties")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
b, err := xml.Marshal(storageServiceProperties)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to marshal request body")
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/xml")
|
||||
err = req.SetBody(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to set request body")
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// setPropertiesResponder handles the response to the SetProperties request.
|
||||
func (client serviceClient) setPropertiesResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK, http.StatusAccepted)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(ioutil.Discard, resp.Response().Body)
|
||||
resp.Response().Body.Close()
|
||||
return &ServiceSetPropertiesResponse{rawResponse: resp.Response()}, err
|
||||
}
|
||||
|
||||
// SubmitBatch the Batch operation allows multiple API calls to be embedded into a single HTTP request.
|
||||
//
|
||||
// body is initial data body will be closed upon successful return. Callers should ensure closure when receiving an
|
||||
// error.contentLength is the length of the request. multipartContentType is required. The value of this header must be
|
||||
// multipart/mixed with a batch boundary. Example header value: multipart/mixed; boundary=batch_<GUID> timeout is the
|
||||
// timeout parameter is expressed in seconds. For more information, see <a
|
||||
// href="https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations">Setting
|
||||
// Timeouts for Blob Service Operations.</a> requestID is provides a client-generated, opaque value with a 1 KB
|
||||
// character limit that is recorded in the analytics logs when storage analytics logging is enabled.
|
||||
func (client serviceClient) SubmitBatch(ctx context.Context, body io.ReadSeeker, contentLength int64, multipartContentType string, timeout *int32, requestID *string) (*SubmitBatchResponse, error) {
|
||||
if err := validate([]validation{
|
||||
{targetValue: body,
|
||||
constraints: []constraint{{target: "body", name: null, rule: true, chain: nil}}},
|
||||
{targetValue: timeout,
|
||||
constraints: []constraint{{target: "timeout", name: null, rule: false,
|
||||
chain: []constraint{{target: "timeout", name: inclusiveMinimum, rule: 0, chain: nil}}}}}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := client.submitBatchPreparer(body, contentLength, multipartContentType, timeout, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.Pipeline().Do(ctx, responderPolicyFactory{responder: client.submitBatchResponder}, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.(*SubmitBatchResponse), err
|
||||
}
|
||||
|
||||
// submitBatchPreparer prepares the SubmitBatch request.
|
||||
func (client serviceClient) submitBatchPreparer(body io.ReadSeeker, contentLength int64, multipartContentType string, timeout *int32, requestID *string) (pipeline.Request, error) {
|
||||
req, err := pipeline.NewRequest("POST", client.url, body)
|
||||
if err != nil {
|
||||
return req, pipeline.NewError(err, "failed to create request")
|
||||
}
|
||||
params := req.URL.Query()
|
||||
if timeout != nil {
|
||||
params.Set("timeout", strconv.FormatInt(int64(*timeout), 10))
|
||||
}
|
||||
params.Set("comp", "batch")
|
||||
req.URL.RawQuery = params.Encode()
|
||||
req.Header.Set("Content-Length", strconv.FormatInt(contentLength, 10))
|
||||
req.Header.Set("Content-Type", multipartContentType)
|
||||
req.Header.Set("x-ms-version", ServiceVersion)
|
||||
if requestID != nil {
|
||||
req.Header.Set("x-ms-client-request-id", *requestID)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// submitBatchResponder handles the response to the SubmitBatch request.
|
||||
func (client serviceClient) submitBatchResponder(resp pipeline.Response) (pipeline.Response, error) {
|
||||
err := validateResponse(resp, http.StatusOK)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SubmitBatchResponse{rawResponse: resp.Response()}, err
|
||||
}
|
367
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_validation.go
generated
vendored
Normal file
367
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_validation.go
generated
vendored
Normal file
|
@ -0,0 +1,367 @@
|
|||
package azblob
|
||||
|
||||
// Code generated by Microsoft (R) AutoRest Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Azure/azure-pipeline-go/pipeline"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Constraint stores constraint name, target field name
|
||||
// Rule and chain validations.
|
||||
type constraint struct {
|
||||
// Target field name for validation.
|
||||
target string
|
||||
|
||||
// Constraint name e.g. minLength, MaxLength, Pattern, etc.
|
||||
name string
|
||||
|
||||
// Rule for constraint e.g. greater than 10, less than 5 etc.
|
||||
rule interface{}
|
||||
|
||||
// Chain validations for struct type
|
||||
chain []constraint
|
||||
}
|
||||
|
||||
// Validation stores parameter-wise validation.
|
||||
type validation struct {
|
||||
targetValue interface{}
|
||||
constraints []constraint
|
||||
}
|
||||
|
||||
// Constraint list
|
||||
const (
|
||||
empty = "Empty"
|
||||
null = "Null"
|
||||
readOnly = "ReadOnly"
|
||||
pattern = "Pattern"
|
||||
maxLength = "MaxLength"
|
||||
minLength = "MinLength"
|
||||
maxItems = "MaxItems"
|
||||
minItems = "MinItems"
|
||||
multipleOf = "MultipleOf"
|
||||
uniqueItems = "UniqueItems"
|
||||
inclusiveMaximum = "InclusiveMaximum"
|
||||
exclusiveMaximum = "ExclusiveMaximum"
|
||||
exclusiveMinimum = "ExclusiveMinimum"
|
||||
inclusiveMinimum = "InclusiveMinimum"
|
||||
)
|
||||
|
||||
// Validate method validates constraints on parameter
|
||||
// passed in validation array.
|
||||
func validate(m []validation) error {
|
||||
for _, item := range m {
|
||||
v := reflect.ValueOf(item.targetValue)
|
||||
for _, constraint := range item.constraints {
|
||||
var err error
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr:
|
||||
err = validatePtr(v, constraint)
|
||||
case reflect.String:
|
||||
err = validateString(v, constraint)
|
||||
case reflect.Struct:
|
||||
err = validateStruct(v, constraint)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
err = validateInt(v, constraint)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
err = validateFloat(v, constraint)
|
||||
case reflect.Array, reflect.Slice, reflect.Map:
|
||||
err = validateArrayMap(v, constraint)
|
||||
default:
|
||||
err = createError(v, constraint, fmt.Sprintf("unknown type %v", v.Kind()))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateStruct(x reflect.Value, v constraint, name ...string) error {
|
||||
//Get field name from target name which is in format a.b.c
|
||||
s := strings.Split(v.target, ".")
|
||||
f := x.FieldByName(s[len(s)-1])
|
||||
if isZero(f) {
|
||||
return createError(x, v, fmt.Sprintf("field %q doesn't exist", v.target))
|
||||
}
|
||||
err := validate([]validation{
|
||||
{
|
||||
targetValue: getInterfaceValue(f),
|
||||
constraints: []constraint{v},
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func validatePtr(x reflect.Value, v constraint) error {
|
||||
if v.name == readOnly {
|
||||
if !x.IsNil() {
|
||||
return createError(x.Elem(), v, "readonly parameter; must send as nil or empty in request")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if x.IsNil() {
|
||||
return checkNil(x, v)
|
||||
}
|
||||
if v.chain != nil {
|
||||
return validate([]validation{
|
||||
{
|
||||
targetValue: getInterfaceValue(x.Elem()),
|
||||
constraints: v.chain,
|
||||
},
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateInt(x reflect.Value, v constraint) error {
|
||||
i := x.Int()
|
||||
r, ok := v.rule.(int)
|
||||
if !ok {
|
||||
return createError(x, v, fmt.Sprintf("rule must be integer value for %v constraint; got: %v", v.name, v.rule))
|
||||
}
|
||||
switch v.name {
|
||||
case multipleOf:
|
||||
if i%int64(r) != 0 {
|
||||
return createError(x, v, fmt.Sprintf("value must be a multiple of %v", r))
|
||||
}
|
||||
case exclusiveMinimum:
|
||||
if i <= int64(r) {
|
||||
return createError(x, v, fmt.Sprintf("value must be greater than %v", r))
|
||||
}
|
||||
case exclusiveMaximum:
|
||||
if i >= int64(r) {
|
||||
return createError(x, v, fmt.Sprintf("value must be less than %v", r))
|
||||
}
|
||||
case inclusiveMinimum:
|
||||
if i < int64(r) {
|
||||
return createError(x, v, fmt.Sprintf("value must be greater than or equal to %v", r))
|
||||
}
|
||||
case inclusiveMaximum:
|
||||
if i > int64(r) {
|
||||
return createError(x, v, fmt.Sprintf("value must be less than or equal to %v", r))
|
||||
}
|
||||
default:
|
||||
return createError(x, v, fmt.Sprintf("constraint %v is not applicable for type integer", v.name))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateFloat(x reflect.Value, v constraint) error {
|
||||
f := x.Float()
|
||||
r, ok := v.rule.(float64)
|
||||
if !ok {
|
||||
return createError(x, v, fmt.Sprintf("rule must be float value for %v constraint; got: %v", v.name, v.rule))
|
||||
}
|
||||
switch v.name {
|
||||
case exclusiveMinimum:
|
||||
if f <= r {
|
||||
return createError(x, v, fmt.Sprintf("value must be greater than %v", r))
|
||||
}
|
||||
case exclusiveMaximum:
|
||||
if f >= r {
|
||||
return createError(x, v, fmt.Sprintf("value must be less than %v", r))
|
||||
}
|
||||
case inclusiveMinimum:
|
||||
if f < r {
|
||||
return createError(x, v, fmt.Sprintf("value must be greater than or equal to %v", r))
|
||||
}
|
||||
case inclusiveMaximum:
|
||||
if f > r {
|
||||
return createError(x, v, fmt.Sprintf("value must be less than or equal to %v", r))
|
||||
}
|
||||
default:
|
||||
return createError(x, v, fmt.Sprintf("constraint %s is not applicable for type float", v.name))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateString(x reflect.Value, v constraint) error {
|
||||
s := x.String()
|
||||
switch v.name {
|
||||
case empty:
|
||||
if len(s) == 0 {
|
||||
return checkEmpty(x, v)
|
||||
}
|
||||
case pattern:
|
||||
reg, err := regexp.Compile(v.rule.(string))
|
||||
if err != nil {
|
||||
return createError(x, v, err.Error())
|
||||
}
|
||||
if !reg.MatchString(s) {
|
||||
return createError(x, v, fmt.Sprintf("value doesn't match pattern %v", v.rule))
|
||||
}
|
||||
case maxLength:
|
||||
if _, ok := v.rule.(int); !ok {
|
||||
return createError(x, v, fmt.Sprintf("rule must be integer value for %v constraint; got: %v", v.name, v.rule))
|
||||
}
|
||||
if len(s) > v.rule.(int) {
|
||||
return createError(x, v, fmt.Sprintf("value length must be less than %v", v.rule))
|
||||
}
|
||||
case minLength:
|
||||
if _, ok := v.rule.(int); !ok {
|
||||
return createError(x, v, fmt.Sprintf("rule must be integer value for %v constraint; got: %v", v.name, v.rule))
|
||||
}
|
||||
if len(s) < v.rule.(int) {
|
||||
return createError(x, v, fmt.Sprintf("value length must be greater than %v", v.rule))
|
||||
}
|
||||
case readOnly:
|
||||
if len(s) > 0 {
|
||||
return createError(reflect.ValueOf(s), v, "readonly parameter; must send as nil or empty in request")
|
||||
}
|
||||
default:
|
||||
return createError(x, v, fmt.Sprintf("constraint %s is not applicable to string type", v.name))
|
||||
}
|
||||
if v.chain != nil {
|
||||
return validate([]validation{
|
||||
{
|
||||
targetValue: getInterfaceValue(x),
|
||||
constraints: v.chain,
|
||||
},
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateArrayMap(x reflect.Value, v constraint) error {
|
||||
switch v.name {
|
||||
case null:
|
||||
if x.IsNil() {
|
||||
return checkNil(x, v)
|
||||
}
|
||||
case empty:
|
||||
if x.IsNil() || x.Len() == 0 {
|
||||
return checkEmpty(x, v)
|
||||
}
|
||||
case maxItems:
|
||||
if _, ok := v.rule.(int); !ok {
|
||||
return createError(x, v, fmt.Sprintf("rule must be integer for %v constraint; got: %v", v.name, v.rule))
|
||||
}
|
||||
if x.Len() > v.rule.(int) {
|
||||
return createError(x, v, fmt.Sprintf("maximum item limit is %v; got: %v", v.rule, x.Len()))
|
||||
}
|
||||
case minItems:
|
||||
if _, ok := v.rule.(int); !ok {
|
||||
return createError(x, v, fmt.Sprintf("rule must be integer for %v constraint; got: %v", v.name, v.rule))
|
||||
}
|
||||
if x.Len() < v.rule.(int) {
|
||||
return createError(x, v, fmt.Sprintf("minimum item limit is %v; got: %v", v.rule, x.Len()))
|
||||
}
|
||||
case uniqueItems:
|
||||
if x.Kind() == reflect.Array || x.Kind() == reflect.Slice {
|
||||
if !checkForUniqueInArray(x) {
|
||||
return createError(x, v, fmt.Sprintf("all items in parameter %q must be unique; got:%v", v.target, x))
|
||||
}
|
||||
} else if x.Kind() == reflect.Map {
|
||||
if !checkForUniqueInMap(x) {
|
||||
return createError(x, v, fmt.Sprintf("all items in parameter %q must be unique; got:%v", v.target, x))
|
||||
}
|
||||
} else {
|
||||
return createError(x, v, fmt.Sprintf("type must be array, slice or map for constraint %v; got: %v", v.name, x.Kind()))
|
||||
}
|
||||
case readOnly:
|
||||
if x.Len() != 0 {
|
||||
return createError(x, v, "readonly parameter; must send as nil or empty in request")
|
||||
}
|
||||
case pattern:
|
||||
reg, err := regexp.Compile(v.rule.(string))
|
||||
if err != nil {
|
||||
return createError(x, v, err.Error())
|
||||
}
|
||||
keys := x.MapKeys()
|
||||
for _, k := range keys {
|
||||
if !reg.MatchString(k.String()) {
|
||||
return createError(k, v, fmt.Sprintf("map key doesn't match pattern %v", v.rule))
|
||||
}
|
||||
}
|
||||
default:
|
||||
return createError(x, v, fmt.Sprintf("constraint %v is not applicable to array, slice and map type", v.name))
|
||||
}
|
||||
if v.chain != nil {
|
||||
return validate([]validation{
|
||||
{
|
||||
targetValue: getInterfaceValue(x),
|
||||
constraints: v.chain,
|
||||
},
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkNil(x reflect.Value, v constraint) error {
|
||||
if _, ok := v.rule.(bool); !ok {
|
||||
return createError(x, v, fmt.Sprintf("rule must be bool value for %v constraint; got: %v", v.name, v.rule))
|
||||
}
|
||||
if v.rule.(bool) {
|
||||
return createError(x, v, "value can not be null; required parameter")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkEmpty(x reflect.Value, v constraint) error {
|
||||
if _, ok := v.rule.(bool); !ok {
|
||||
return createError(x, v, fmt.Sprintf("rule must be bool value for %v constraint; got: %v", v.name, v.rule))
|
||||
}
|
||||
if v.rule.(bool) {
|
||||
return createError(x, v, "value can not be null or empty; required parameter")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkForUniqueInArray(x reflect.Value) bool {
|
||||
if x == reflect.Zero(reflect.TypeOf(x)) || x.Len() == 0 {
|
||||
return false
|
||||
}
|
||||
arrOfInterface := make([]interface{}, x.Len())
|
||||
for i := 0; i < x.Len(); i++ {
|
||||
arrOfInterface[i] = x.Index(i).Interface()
|
||||
}
|
||||
m := make(map[interface{}]bool)
|
||||
for _, val := range arrOfInterface {
|
||||
if m[val] {
|
||||
return false
|
||||
}
|
||||
m[val] = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func checkForUniqueInMap(x reflect.Value) bool {
|
||||
if x == reflect.Zero(reflect.TypeOf(x)) || x.Len() == 0 {
|
||||
return false
|
||||
}
|
||||
mapOfInterface := make(map[interface{}]interface{}, x.Len())
|
||||
keys := x.MapKeys()
|
||||
for _, k := range keys {
|
||||
mapOfInterface[k.Interface()] = x.MapIndex(k).Interface()
|
||||
}
|
||||
m := make(map[interface{}]bool)
|
||||
for _, val := range mapOfInterface {
|
||||
if m[val] {
|
||||
return false
|
||||
}
|
||||
m[val] = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getInterfaceValue(x reflect.Value) interface{} {
|
||||
if x.Kind() == reflect.Invalid {
|
||||
return nil
|
||||
}
|
||||
return x.Interface()
|
||||
}
|
||||
|
||||
func isZero(x interface{}) bool {
|
||||
return x == reflect.Zero(reflect.TypeOf(x)).Interface()
|
||||
}
|
||||
|
||||
func createError(x reflect.Value, v constraint, message string) error {
|
||||
return pipeline.NewError(nil, fmt.Sprintf("validation failed: parameter=%s constraint=%s value=%#v details: %s",
|
||||
v.target, v.name, getInterfaceValue(x), message))
|
||||
}
|
14
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_version.go
generated
vendored
Normal file
14
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_generated_version.go
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
package azblob
|
||||
|
||||
// Code generated by Microsoft (R) AutoRest Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
// UserAgent returns the UserAgent string to use when sending http.Requests.
|
||||
func UserAgent() string {
|
||||
return "Azure-SDK-For-Go/0.0.0 azblob/2019-02-02"
|
||||
}
|
||||
|
||||
// Version returns the semantic version (see http://semver.org) of the client.
|
||||
func Version() string {
|
||||
return "0.0.0"
|
||||
}
|
242
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_response_helpers.go
generated
vendored
Normal file
242
vendor/github.com/Azure/azure-storage-blob-go/azblob/zz_response_helpers.go
generated
vendored
Normal file
|
@ -0,0 +1,242 @@
|
|||
package azblob
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BlobHTTPHeaders contains read/writeable blob properties.
|
||||
type BlobHTTPHeaders struct {
|
||||
ContentType string
|
||||
ContentMD5 []byte
|
||||
ContentEncoding string
|
||||
ContentLanguage string
|
||||
ContentDisposition string
|
||||
CacheControl string
|
||||
}
|
||||
|
||||
// NewHTTPHeaders returns the user-modifiable properties for this blob.
|
||||
func (bgpr BlobGetPropertiesResponse) NewHTTPHeaders() BlobHTTPHeaders {
|
||||
return BlobHTTPHeaders{
|
||||
ContentType: bgpr.ContentType(),
|
||||
ContentEncoding: bgpr.ContentEncoding(),
|
||||
ContentLanguage: bgpr.ContentLanguage(),
|
||||
ContentDisposition: bgpr.ContentDisposition(),
|
||||
CacheControl: bgpr.CacheControl(),
|
||||
ContentMD5: bgpr.ContentMD5(),
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// NewHTTPHeaders returns the user-modifiable properties for this blob.
|
||||
func (dr downloadResponse) NewHTTPHeaders() BlobHTTPHeaders {
|
||||
return BlobHTTPHeaders{
|
||||
ContentType: dr.ContentType(),
|
||||
ContentEncoding: dr.ContentEncoding(),
|
||||
ContentLanguage: dr.ContentLanguage(),
|
||||
ContentDisposition: dr.ContentDisposition(),
|
||||
CacheControl: dr.CacheControl(),
|
||||
ContentMD5: dr.ContentMD5(),
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// DownloadResponse wraps AutoRest generated DownloadResponse and helps to provide info for retry.
|
||||
type DownloadResponse struct {
|
||||
r *downloadResponse
|
||||
ctx context.Context
|
||||
b BlobURL
|
||||
getInfo HTTPGetterInfo
|
||||
}
|
||||
|
||||
// Body constructs new RetryReader stream for reading data. If a connection failes
|
||||
// while reading, it will make additional requests to reestablish a connection and
|
||||
// continue reading. Specifying a RetryReaderOption's with MaxRetryRequests set to 0
|
||||
// (the default), returns the original response body and no retries will be performed.
|
||||
func (r *DownloadResponse) Body(o RetryReaderOptions) io.ReadCloser {
|
||||
if o.MaxRetryRequests == 0 { // No additional retries
|
||||
return r.Response().Body
|
||||
}
|
||||
return NewRetryReader(r.ctx, r.Response(), r.getInfo, o,
|
||||
func(ctx context.Context, getInfo HTTPGetterInfo) (*http.Response, error) {
|
||||
resp, err := r.b.Download(ctx, getInfo.Offset, getInfo.Count,
|
||||
BlobAccessConditions{
|
||||
ModifiedAccessConditions: ModifiedAccessConditions{IfMatch: getInfo.ETag},
|
||||
},
|
||||
false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Response(), err
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Response returns the raw HTTP response object.
|
||||
func (r DownloadResponse) Response() *http.Response {
|
||||
return r.r.Response()
|
||||
}
|
||||
|
||||
// NewHTTPHeaders returns the user-modifiable properties for this blob.
|
||||
func (r DownloadResponse) NewHTTPHeaders() BlobHTTPHeaders {
|
||||
return r.r.NewHTTPHeaders()
|
||||
}
|
||||
|
||||
// BlobContentMD5 returns the value for header x-ms-blob-content-md5.
|
||||
func (r DownloadResponse) BlobContentMD5() []byte {
|
||||
return r.r.BlobContentMD5()
|
||||
}
|
||||
|
||||
// ContentMD5 returns the value for header Content-MD5.
|
||||
func (r DownloadResponse) ContentMD5() []byte {
|
||||
return r.r.ContentMD5()
|
||||
}
|
||||
|
||||
// StatusCode returns the HTTP status code of the response, e.g. 200.
|
||||
func (r DownloadResponse) StatusCode() int {
|
||||
return r.r.StatusCode()
|
||||
}
|
||||
|
||||
// Status returns the HTTP status message of the response, e.g. "200 OK".
|
||||
func (r DownloadResponse) Status() string {
|
||||
return r.r.Status()
|
||||
}
|
||||
|
||||
// AcceptRanges returns the value for header Accept-Ranges.
|
||||
func (r DownloadResponse) AcceptRanges() string {
|
||||
return r.r.AcceptRanges()
|
||||
}
|
||||
|
||||
// BlobCommittedBlockCount returns the value for header x-ms-blob-committed-block-count.
|
||||
func (r DownloadResponse) BlobCommittedBlockCount() int32 {
|
||||
return r.r.BlobCommittedBlockCount()
|
||||
}
|
||||
|
||||
// BlobSequenceNumber returns the value for header x-ms-blob-sequence-number.
|
||||
func (r DownloadResponse) BlobSequenceNumber() int64 {
|
||||
return r.r.BlobSequenceNumber()
|
||||
}
|
||||
|
||||
// BlobType returns the value for header x-ms-blob-type.
|
||||
func (r DownloadResponse) BlobType() BlobType {
|
||||
return r.r.BlobType()
|
||||
}
|
||||
|
||||
// CacheControl returns the value for header Cache-Control.
|
||||
func (r DownloadResponse) CacheControl() string {
|
||||
return r.r.CacheControl()
|
||||
}
|
||||
|
||||
// ContentDisposition returns the value for header Content-Disposition.
|
||||
func (r DownloadResponse) ContentDisposition() string {
|
||||
return r.r.ContentDisposition()
|
||||
}
|
||||
|
||||
// ContentEncoding returns the value for header Content-Encoding.
|
||||
func (r DownloadResponse) ContentEncoding() string {
|
||||
return r.r.ContentEncoding()
|
||||
}
|
||||
|
||||
// ContentLanguage returns the value for header Content-Language.
|
||||
func (r DownloadResponse) ContentLanguage() string {
|
||||
return r.r.ContentLanguage()
|
||||
}
|
||||
|
||||
// ContentLength returns the value for header Content-Length.
|
||||
func (r DownloadResponse) ContentLength() int64 {
|
||||
return r.r.ContentLength()
|
||||
}
|
||||
|
||||
// ContentRange returns the value for header Content-Range.
|
||||
func (r DownloadResponse) ContentRange() string {
|
||||
return r.r.ContentRange()
|
||||
}
|
||||
|
||||
// ContentType returns the value for header Content-Type.
|
||||
func (r DownloadResponse) ContentType() string {
|
||||
return r.r.ContentType()
|
||||
}
|
||||
|
||||
// CopyCompletionTime returns the value for header x-ms-copy-completion-time.
|
||||
func (r DownloadResponse) CopyCompletionTime() time.Time {
|
||||
return r.r.CopyCompletionTime()
|
||||
}
|
||||
|
||||
// CopyID returns the value for header x-ms-copy-id.
|
||||
func (r DownloadResponse) CopyID() string {
|
||||
return r.r.CopyID()
|
||||
}
|
||||
|
||||
// CopyProgress returns the value for header x-ms-copy-progress.
|
||||
func (r DownloadResponse) CopyProgress() string {
|
||||
return r.r.CopyProgress()
|
||||
}
|
||||
|
||||
// CopySource returns the value for header x-ms-copy-source.
|
||||
func (r DownloadResponse) CopySource() string {
|
||||
return r.r.CopySource()
|
||||
}
|
||||
|
||||
// CopyStatus returns the value for header x-ms-copy-status.
|
||||
func (r DownloadResponse) CopyStatus() CopyStatusType {
|
||||
return r.r.CopyStatus()
|
||||
}
|
||||
|
||||
// CopyStatusDescription returns the value for header x-ms-copy-status-description.
|
||||
func (r DownloadResponse) CopyStatusDescription() string {
|
||||
return r.r.CopyStatusDescription()
|
||||
}
|
||||
|
||||
// Date returns the value for header Date.
|
||||
func (r DownloadResponse) Date() time.Time {
|
||||
return r.r.Date()
|
||||
}
|
||||
|
||||
// ETag returns the value for header ETag.
|
||||
func (r DownloadResponse) ETag() ETag {
|
||||
return r.r.ETag()
|
||||
}
|
||||
|
||||
// IsServerEncrypted returns the value for header x-ms-server-encrypted.
|
||||
func (r DownloadResponse) IsServerEncrypted() string {
|
||||
return r.r.IsServerEncrypted()
|
||||
}
|
||||
|
||||
// LastModified returns the value for header Last-Modified.
|
||||
func (r DownloadResponse) LastModified() time.Time {
|
||||
return r.r.LastModified()
|
||||
}
|
||||
|
||||
// LeaseDuration returns the value for header x-ms-lease-duration.
|
||||
func (r DownloadResponse) LeaseDuration() LeaseDurationType {
|
||||
return r.r.LeaseDuration()
|
||||
}
|
||||
|
||||
// LeaseState returns the value for header x-ms-lease-state.
|
||||
func (r DownloadResponse) LeaseState() LeaseStateType {
|
||||
return r.r.LeaseState()
|
||||
}
|
||||
|
||||
// LeaseStatus returns the value for header x-ms-lease-status.
|
||||
func (r DownloadResponse) LeaseStatus() LeaseStatusType {
|
||||
return r.r.LeaseStatus()
|
||||
}
|
||||
|
||||
// RequestID returns the value for header x-ms-request-id.
|
||||
func (r DownloadResponse) RequestID() string {
|
||||
return r.r.RequestID()
|
||||
}
|
||||
|
||||
// Version returns the value for header x-ms-version.
|
||||
func (r DownloadResponse) Version() string {
|
||||
return r.r.Version()
|
||||
}
|
||||
|
||||
// NewMetadata returns user-defined key/value pairs.
|
||||
func (r DownloadResponse) NewMetadata() Metadata {
|
||||
return r.r.NewMetadata()
|
||||
}
|
|
@ -771,8 +771,9 @@ func (spt *ServicePrincipalToken) EnsureFresh() error {
|
|||
// EnsureFreshWithContext will refresh the token if it will expire within the refresh window (as set by
|
||||
// RefreshWithin) and autoRefresh flag is on. This method is safe for concurrent use.
|
||||
func (spt *ServicePrincipalToken) EnsureFreshWithContext(ctx context.Context) error {
|
||||
if spt.inner.AutoRefresh && spt.inner.Token.WillExpireIn(spt.inner.RefreshWithin) {
|
||||
// take the write lock then check to see if the token was already refreshed
|
||||
// must take the read lock when initially checking the token's expiration
|
||||
if spt.inner.AutoRefresh && spt.Token().WillExpireIn(spt.inner.RefreshWithin) {
|
||||
// take the write lock then check again to see if the token was already refreshed
|
||||
spt.refreshLock.Lock()
|
||||
defer spt.refreshLock.Unlock()
|
||||
if spt.inner.Token.WillExpireIn(spt.inner.RefreshWithin) {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
.idea/*
|
|
@ -0,0 +1,11 @@
|
|||
package ieproxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// GetProxyFunc is a forwarder for the OS-Exclusive proxyMiddleman_os.go files
|
||||
func GetProxyFunc() func(*http.Request) (*url.URL, error) {
|
||||
return proxyMiddleman()
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2014 mattn
|
||||
Copyright (c) 2017 oliverpool
|
||||
Copyright (c) 2019 Adele Reed
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,49 @@
|
|||
# ieproxy
|
||||
|
||||
Go package to detect the proxy settings on Windows platform.
|
||||
|
||||
The settings are initially attempted to be read from the [`WinHttpGetIEProxyConfigForCurrentUser` DLL call](https://docs.microsoft.com/en-us/windows/desktop/api/winhttp/nf-winhttp-winhttpgetieproxyconfigforcurrentuser), but falls back to the registry (`CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings`) in the event the DLL call fails.
|
||||
|
||||
For more information, take a look at the [documentation](https://godoc.org/github.com/mattn/go-ieproxy)
|
||||
|
||||
## Methods
|
||||
|
||||
You can either obtain a `net/http` compatible proxy function using `ieproxy.GetProxyFunc()`, set environment variables using `ieproxy.OverrideEnvWithStaticProxy()` (though no automatic configuration is available this way), or obtain the proxy settings via `ieproxy.GetConf()`.
|
||||
|
||||
| Method | Supported configuration options: |
|
||||
|----------------------------------------|-----------------------------------------------|
|
||||
| `ieproxy.GetProxyFunc()` | Static, Specified script, and fully automatic |
|
||||
| `ieproxy.OverrideEnvWithStaticProxy()` | Static |
|
||||
| `ieproxy.GetConf()` | Depends on how you use it |
|
||||
|
||||
## Examples
|
||||
|
||||
### Using GetProxyFunc():
|
||||
|
||||
```go
|
||||
func init() {
|
||||
http.DefaultTransport.(*http.Transport).Proxy = ieproxy.GetProxyFunc()
|
||||
}
|
||||
```
|
||||
|
||||
GetProxyFunc acts as a middleman between `net/http` and `mattn/go-ieproxy` in order to select the correct proxy configuration based off the details supplied in the config.
|
||||
|
||||
### Using OverrideEnvWithStaticProxy():
|
||||
|
||||
```go
|
||||
func init() {
|
||||
ieproxy.OverrideEnvWithStaticProxy()
|
||||
http.DefaultTransport.(*http.Transport).Proxy = http.ProxyFromEnvironment
|
||||
}
|
||||
```
|
||||
|
||||
OverrideEnvWithStaticProxy overrides the relevant environment variables (`HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`) with the **static, manually configured** proxy details typically found in the registry.
|
||||
|
||||
### Using GetConf():
|
||||
|
||||
```go
|
||||
func main() {
|
||||
conf := ieproxy.GetConf()
|
||||
//Handle proxies how you want to.
|
||||
}
|
||||
```
|
|
@ -0,0 +1,51 @@
|
|||
// Package ieproxy is a utility to retrieve the proxy parameters (especially of Internet Explorer on windows)
|
||||
//
|
||||
// On windows, it gathers the parameters from the registry (regedit), while it uses env variable on other platforms
|
||||
package ieproxy
|
||||
|
||||
import "os"
|
||||
|
||||
// ProxyConf gathers the configuration for proxy
|
||||
type ProxyConf struct {
|
||||
Static StaticProxyConf // static configuration
|
||||
Automatic ProxyScriptConf // script configuration
|
||||
}
|
||||
|
||||
// StaticProxyConf contains the configuration for static proxy
|
||||
type StaticProxyConf struct {
|
||||
// Is the proxy active?
|
||||
Active bool
|
||||
// Proxy address for each scheme (http, https)
|
||||
// "" (empty string) is the fallback proxy
|
||||
Protocols map[string]string
|
||||
// Addresses not to be browsed via the proxy (comma-separated, linux-like)
|
||||
NoProxy string
|
||||
}
|
||||
|
||||
// ProxyScriptConf contains the configuration for automatic proxy
|
||||
type ProxyScriptConf struct {
|
||||
// Is the proxy active?
|
||||
Active bool
|
||||
// PreConfiguredURL of the .pac file.
|
||||
// If this is empty and Active is true, auto-configuration should be assumed.
|
||||
PreConfiguredURL string
|
||||
}
|
||||
|
||||
// GetConf retrieves the proxy configuration from the Windows Regedit
|
||||
func GetConf() ProxyConf {
|
||||
return getConf()
|
||||
}
|
||||
|
||||
// OverrideEnvWithStaticProxy writes new values to the
|
||||
// `http_proxy`, `https_proxy` and `no_proxy` environment variables.
|
||||
// The values are taken from the Windows Regedit (should be called in `init()` function - see example)
|
||||
func OverrideEnvWithStaticProxy() {
|
||||
overrideEnvWithStaticProxy(GetConf(), os.Setenv)
|
||||
}
|
||||
|
||||
// FindProxyForURL computes the proxy for a given URL according to the pac file
|
||||
func (psc *ProxyScriptConf) FindProxyForURL(URL string) string {
|
||||
return psc.findProxyForURL(URL)
|
||||
}
|
||||
|
||||
type envSetter func(string, string) error
|
|
@ -0,0 +1,10 @@
|
|||
// +build !windows
|
||||
|
||||
package ieproxy
|
||||
|
||||
func getConf() ProxyConf {
|
||||
return ProxyConf{}
|
||||
}
|
||||
|
||||
func overrideEnvWithStaticProxy(pc ProxyConf, setenv envSetter) {
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
package ieproxy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
type regeditValues struct {
|
||||
ProxyServer string
|
||||
ProxyOverride string
|
||||
ProxyEnable uint64
|
||||
AutoConfigURL string
|
||||
}
|
||||
|
||||
var once sync.Once
|
||||
var windowsProxyConf ProxyConf
|
||||
|
||||
// GetConf retrieves the proxy configuration from the Windows Regedit
|
||||
func getConf() ProxyConf {
|
||||
once.Do(writeConf)
|
||||
return windowsProxyConf
|
||||
}
|
||||
|
||||
func writeConf() {
|
||||
var (
|
||||
cfg *tWINHTTP_CURRENT_USER_IE_PROXY_CONFIG
|
||||
err error
|
||||
)
|
||||
|
||||
if cfg, err = getUserConfigFromWindowsSyscall(); err != nil {
|
||||
regedit, _ := readRegedit() // If the syscall fails, backup to manual detection.
|
||||
windowsProxyConf = parseRegedit(regedit)
|
||||
return
|
||||
}
|
||||
|
||||
defer globalFreeWrapper(cfg.lpszProxy)
|
||||
defer globalFreeWrapper(cfg.lpszProxyBypass)
|
||||
defer globalFreeWrapper(cfg.lpszAutoConfigUrl)
|
||||
|
||||
windowsProxyConf = ProxyConf{
|
||||
Static: StaticProxyConf{
|
||||
Active: cfg.lpszProxy != nil,
|
||||
},
|
||||
Automatic: ProxyScriptConf{
|
||||
Active: cfg.lpszAutoConfigUrl != nil || cfg.fAutoDetect,
|
||||
},
|
||||
}
|
||||
|
||||
if windowsProxyConf.Static.Active {
|
||||
protocol := make(map[string]string)
|
||||
for _, s := range strings.Split(StringFromUTF16Ptr(cfg.lpszProxy), ";") {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
pair := strings.SplitN(s, "=", 2)
|
||||
if len(pair) > 1 {
|
||||
protocol[pair[0]] = pair[1]
|
||||
} else {
|
||||
protocol[""] = pair[0]
|
||||
}
|
||||
}
|
||||
|
||||
windowsProxyConf.Static.Protocols = protocol
|
||||
if cfg.lpszProxyBypass != nil {
|
||||
windowsProxyConf.Static.NoProxy = strings.Replace(StringFromUTF16Ptr(cfg.lpszProxyBypass), ";", ",", -1)
|
||||
}
|
||||
}
|
||||
|
||||
if windowsProxyConf.Automatic.Active {
|
||||
windowsProxyConf.Automatic.PreConfiguredURL = StringFromUTF16Ptr(cfg.lpszAutoConfigUrl)
|
||||
}
|
||||
}
|
||||
|
||||
func getUserConfigFromWindowsSyscall() (*tWINHTTP_CURRENT_USER_IE_PROXY_CONFIG, error) {
|
||||
handle, _, err := winHttpOpen.Call(0, 0, 0, 0, 0)
|
||||
if handle == 0 {
|
||||
return &tWINHTTP_CURRENT_USER_IE_PROXY_CONFIG{}, err
|
||||
}
|
||||
defer winHttpCloseHandle.Call(handle)
|
||||
|
||||
config := new(tWINHTTP_CURRENT_USER_IE_PROXY_CONFIG)
|
||||
|
||||
ret, _, err := winHttpGetIEProxyConfigForCurrentUser.Call(uintptr(unsafe.Pointer(config)))
|
||||
if ret > 0 {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return config, err
|
||||
}
|
||||
|
||||
// OverrideEnvWithStaticProxy writes new values to the
|
||||
// http_proxy, https_proxy and no_proxy environment variables.
|
||||
// The values are taken from the Windows Regedit (should be called in init() function)
|
||||
func overrideEnvWithStaticProxy(conf ProxyConf, setenv envSetter) {
|
||||
if conf.Static.Active {
|
||||
for _, scheme := range []string{"http", "https"} {
|
||||
url := mapFallback(scheme, "", conf.Static.Protocols)
|
||||
setenv(scheme+"_proxy", url)
|
||||
}
|
||||
if conf.Static.NoProxy != "" {
|
||||
setenv("no_proxy", conf.Static.NoProxy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseRegedit(regedit regeditValues) ProxyConf {
|
||||
protocol := make(map[string]string)
|
||||
for _, s := range strings.Split(regedit.ProxyServer, ";") {
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
pair := strings.SplitN(s, "=", 2)
|
||||
if len(pair) > 1 {
|
||||
protocol[pair[0]] = pair[1]
|
||||
} else {
|
||||
protocol[""] = pair[0]
|
||||
}
|
||||
}
|
||||
|
||||
return ProxyConf{
|
||||
Static: StaticProxyConf{
|
||||
Active: regedit.ProxyEnable > 0,
|
||||
Protocols: protocol,
|
||||
NoProxy: strings.Replace(regedit.ProxyOverride, ";", ",", -1), // to match linux style
|
||||
},
|
||||
Automatic: ProxyScriptConf{
|
||||
Active: regedit.AutoConfigURL != "",
|
||||
PreConfiguredURL: regedit.AutoConfigURL,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func readRegedit() (values regeditValues, err error) {
|
||||
k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
values.ProxyServer, _, err = k.GetStringValue("ProxyServer")
|
||||
if err != nil && err != registry.ErrNotExist {
|
||||
return
|
||||
}
|
||||
values.ProxyOverride, _, err = k.GetStringValue("ProxyOverride")
|
||||
if err != nil && err != registry.ErrNotExist {
|
||||
return
|
||||
}
|
||||
|
||||
values.ProxyEnable, _, err = k.GetIntegerValue("ProxyEnable")
|
||||
if err != nil && err != registry.ErrNotExist {
|
||||
return
|
||||
}
|
||||
|
||||
values.AutoConfigURL, _, err = k.GetStringValue("AutoConfigURL")
|
||||
if err != nil && err != registry.ErrNotExist {
|
||||
return
|
||||
}
|
||||
err = nil
|
||||
return
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue