diff --git a/Dockerfile b/Dockerfile
index c1bf8bd5f..266c78634 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,7 +6,7 @@ RUN apt-get update && \
ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution
ENV GOPATH $DISTRIBUTION_DIR/Godeps/_workspace:$GOPATH
-ENV DOCKER_BUILDTAGS include_rados include_azure
+ENV DOCKER_BUILDTAGS include_rados
WORKDIR $DISTRIBUTION_DIR
COPY . $DISTRIBUTION_DIR
diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index 1e8d5d01a..f0b9d21a0 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -18,9 +18,8 @@
"Rev": "cc210f45dcb9889c2769a274522be2bf70edfb99"
},
{
- "ImportPath": "github.com/MSOpenTech/azure-sdk-for-go/storage",
- "Comment": "v1.2-43-gd90753b",
- "Rev": "d90753bcad2ed782fcead7392d1e831df29aa2bb"
+ "ImportPath": "github.com/Azure/azure-sdk-for-go/storage",
+ "Rev": "97d9593768bbbbd316f9c055dfc5f780933cd7fc"
},
{
"ImportPath": "github.com/Sirupsen/logrus",
diff --git a/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/.gitignore b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/.gitignore
new file mode 100644
index 000000000..c4c1f537b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/.gitignore
@@ -0,0 +1,29 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
+
+# Editor swap files
+*.swp
+*~
+.DS_Store
diff --git a/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/.travis.yml b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/.travis.yml
new file mode 100644
index 000000000..e6fabccbc
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/.travis.yml
@@ -0,0 +1,19 @@
+sudo: false
+
+language: go
+
+before_script:
+ - go get -u golang.org/x/tools/cmd/vet
+ - go get -u github.com/golang/lint/golint
+
+go: tip
+script:
+ - test -z "$(gofmt -s -l -w management | tee /dev/stderr)"
+ - test -z "$(gofmt -s -l -w storage | tee /dev/stderr)"
+ - go build -v ./...
+ - go test -v ./storage/... -check.v
+ - test -z "$(golint ./storage/... | tee /dev/stderr)"
+ - go vet ./storage/...
+ - go test -v ./management/...
+ - test -z "$(golint ./management/... | grep -v 'should have comment' | grep -v 'stutters' | tee /dev/stderr)"
+ - go vet ./management/...
diff --git a/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/LICENSE b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/LICENSE
new file mode 100644
index 000000000..d64569567
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/README.md b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/README.md
new file mode 100644
index 000000000..13d548570
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/README.md
@@ -0,0 +1,88 @@
+# Microsoft Azure SDK for Go
+
+This project provides various Go packages to perform operations
+on Microsoft Azure REST APIs.
+
+[![GoDoc](https://godoc.org/github.com/Azure/azure-sdk-for-go?status.svg)](https://godoc.org/github.com/Azure/azure-sdk-for-go) [![Build Status](https://travis-ci.org/Azure/azure-sdk-for-go.svg?branch=master)](https://travis-ci.org/Azure/azure-sdk-for-go)
+
+See list of implemented API clients [here](http://godoc.org/github.com/Azure/azure-sdk-for-go).
+
+> **NOTE:** This repository is under heavy ongoing development and
+is likely to break over time. We currently do not have any releases
+yet. If you are planning to use the repository, please consider vendoring
+the packages in your project and update them when a stable tag is out.
+
+# Installation
+
+ go get -d github.com/Azure/azure-sdk-for-go/management
+
+# Usage
+
+Read Godoc of the repository at: http://godoc.org/github.com/Azure/azure-sdk-for-go/
+
+The client currently supports authentication to the Service Management
+API with certificates or Azure `.publishSettings` file. You can
+download the `.publishSettings` file for your subscriptions
+[here](https://manage.windowsazure.com/publishsettings).
+
+### Example: Creating a Linux Virtual Machine
+
+```go
+package main
+
+import (
+ "encoding/base64"
+ "fmt"
+
+ "github.com/Azure/azure-sdk-for-go/management"
+ "github.com/Azure/azure-sdk-for-go/management/hostedservice"
+ "github.com/Azure/azure-sdk-for-go/management/virtualmachine"
+ "github.com/Azure/azure-sdk-for-go/management/vmutils"
+)
+
+func main() {
+ dnsName := "test-vm-from-go"
+ storageAccount := "mystorageaccount"
+ location := "West US"
+ vmSize := "Small"
+ vmImage := "b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-14_04-LTS-amd64-server-20140724-en-us-30GB"
+ userName := "testuser"
+ userPassword := "Test123"
+
+ client, err := management.ClientFromPublishSettingsFile("path/to/downloaded.publishsettings", "")
+ if err != nil {
+ panic(err)
+ }
+
+ // create hosted service
+ if err := hostedservice.NewClient(client).CreateHostedService(hostedservice.CreateHostedServiceParameters{
+ ServiceName: dnsName,
+ Location: location,
+ Label: base64.StdEncoding.EncodeToString([]byte(dnsName))}); err != nil {
+ panic(err)
+ }
+
+ // create virtual machine
+ role := vmutils.NewVMConfiguration(dnsName, vmSize)
+ vmutils.ConfigureDeploymentFromPlatformImage(
+ &role,
+ vmImage,
+ fmt.Sprintf("http://%s.blob.core.windows.net/sdktest/%s.vhd", storageAccount, dnsName),
+ "")
+ vmutils.ConfigureForLinux(&role, dnsName, userName, userPassword)
+ vmutils.ConfigureWithPublicSSH(&role)
+
+ operationID, err := virtualmachine.NewClient(client).
+ CreateDeployment(role, dnsName, virtualmachine.CreateDeploymentOptions{})
+ if err != nil {
+ panic(err)
+ }
+ if err := client.WaitForOperation(operationID, nil); err != nil {
+ panic(err)
+ }
+}
+```
+
+# License
+
+This project is published under [Apache 2.0 License](LICENSE).
diff --git a/Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/storage/blob.go b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/blob.go
similarity index 69%
rename from Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/storage/blob.go
rename to Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/blob.go
index f451e51fe..a6e2eb8ab 100644
--- a/Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/storage/blob.go
+++ b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/blob.go
@@ -2,7 +2,6 @@ package storage
import (
"bytes"
- "encoding/base64"
"encoding/xml"
"errors"
"fmt"
@@ -14,8 +13,10 @@ import (
"time"
)
+// BlobStorageClient contains operations for Microsoft Azure Blob Storage
+// Service.
type BlobStorageClient struct {
- client StorageClient
+ client Client
}
// A Container is an entry in ContainerListResponse.
@@ -25,8 +26,8 @@ type Container struct {
// TODO (ahmetalpbalkan) Metadata
}
-// ContainerProperties contains various properties of a
-// container returned from various endpoints like ListContainers.
+// 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"`
@@ -37,7 +38,9 @@ type ContainerProperties struct {
}
// ContainerListResponse contains the response fields from
-// ListContainers call. https://msdn.microsoft.com/en-us/library/azure/dd179352.aspx
+// 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"`
@@ -66,7 +69,7 @@ type BlobProperties struct {
ContentEncoding string `xml:"Content-Encoding"`
BlobType BlobType `xml:"x-ms-blob-blob-type"`
SequenceNumber int64 `xml:"x-ms-blob-sequence-number"`
- CopyId string `xml:"CopyId"`
+ CopyID string `xml:"CopyId"`
CopyStatus string `xml:"CopyStatus"`
CopySource string `xml:"CopySource"`
CopyProgress string `xml:"CopyProgress"`
@@ -74,8 +77,9 @@ type BlobProperties struct {
CopyStatusDescription string `xml:"CopyStatusDescription"`
}
-// BlobListResponse contains the response fields from
-// ListBlobs call. https://msdn.microsoft.com/en-us/library/azure/dd135734.aspx
+// 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"`
@@ -86,8 +90,10 @@ type BlobListResponse struct {
Blobs []Blob `xml:"Blobs>Blob"`
}
-// ListContainersParameters defines the set of customizable
-// parameters to make a List Containers call. https://msdn.microsoft.com/en-us/library/azure/dd179352.aspx
+// 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
@@ -119,7 +125,9 @@ func (p ListContainersParameters) getParameters() url.Values {
}
// ListBlobsParameters defines the set of customizable
-// parameters to make a List Blobs call. https://msdn.microsoft.com/en-us/library/azure/dd135734.aspx
+// 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
@@ -157,6 +165,7 @@ func (p ListBlobsParameters) getParameters() url.Values {
// BlobType defines the type of the Azure Blob.
type BlobType string
+// Types of page blobs
const (
BlobTypeBlock BlobType = "BlockBlob"
BlobTypePage BlobType = "PageBlob"
@@ -166,6 +175,7 @@ const (
// done on the page blob.
type PageWriteType string
+// Types of operations on page blobs
const (
PageWriteTypeUpdate PageWriteType = "update"
PageWriteTypeClear PageWriteType = "clear"
@@ -178,29 +188,35 @@ const (
blobCopyStatusFailed = "failed"
)
-// 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.
+// 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"
)
-// 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.
+// 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"
)
+// Maximum sizes (per REST API) for various concepts
const (
MaxBlobBlockSize = 4 * 1024 * 1024
MaxBlobPageSize = 4 * 1024 * 1024
@@ -210,6 +226,7 @@ const (
// 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"
@@ -219,12 +236,13 @@ const (
// Block is used to create Block entities for Put Block List
// call.
type Block struct {
- Id string
+ ID string
Status BlockStatus
}
-// BlockListResponse contains the response fields from
-// Get Block List call. https://msdn.microsoft.com/en-us/library/azure/dd179400.aspx
+// 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"`
@@ -239,31 +257,32 @@ type BlockResponse struct {
}
// GetPageRangesResponse contains the reponse fields from
-// Get Page Ranges call. https://msdn.microsoft.com/en-us/library/azure/ee691973.aspx
+// 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. https://msdn.microsoft.com/en-us/library/azure/ee691973.aspx
+// 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 (
- ErrNotCreated = errors.New("storage: operation has returned a successful error code other than 201 Created.")
- ErrNotAccepted = errors.New("storage: operation has returned a successful error code other than 202 Accepted.")
-
errBlobCopyAborted = errors.New("storage: blob copy is aborted")
- errBlobCopyIdMismatch = errors.New("storage: blob copy id is a mismatch")
+ errBlobCopyIDMismatch = errors.New("storage: blob copy id is a mismatch")
)
-const errUnexpectedStatus = "storage: was expecting status code: %d, got: %d"
-
// 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
+// 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)
@@ -274,31 +293,34 @@ func (b BlobStorageClient) ListContainers(params ListContainersParameters) (Cont
if err != nil {
return out, err
}
+ defer resp.body.Close()
err = xmlUnmarshal(resp.body, &out)
return out, err
}
// CreateContainer creates a blob container within the storage account
-// with given name and access level. See https://msdn.microsoft.com/en-us/library/azure/dd179468.aspx
-// Returns error if container already exists.
+// with given name and access level. Returns error if container already exists.
+//
+// See https://msdn.microsoft.com/en-us/library/azure/dd179468.aspx
func (b BlobStorageClient) CreateContainer(name string, access ContainerAccessType) error {
resp, err := b.createContainer(name, access)
if err != nil {
return err
}
- if resp.statusCode != http.StatusCreated {
- return ErrNotCreated
- }
- return nil
+ defer resp.body.Close()
+ return checkRespCode(resp.statusCode, []int{http.StatusCreated})
}
// CreateContainerIfNotExists creates a blob container if it does not exist. Returns
// true if container is newly created or false if container already exists.
func (b BlobStorageClient) CreateContainerIfNotExists(name string, access ContainerAccessType) (bool, error) {
resp, err := b.createContainer(name, access)
- if resp != nil && (resp.statusCode == http.StatusCreated || resp.statusCode == http.StatusConflict) {
- return resp.statusCode == http.StatusCreated, nil
+ if resp != nil {
+ defer resp.body.Close()
+ if resp.statusCode == http.StatusCreated || resp.statusCode == http.StatusConflict {
+ return resp.statusCode == http.StatusCreated, nil
+ }
}
return false, err
}
@@ -323,34 +345,41 @@ func (b BlobStorageClient) ContainerExists(name string) (bool, error) {
headers := b.client.getStandardHeaders()
resp, err := b.client.exec(verb, uri, headers, nil)
- if resp != nil && (resp.statusCode == http.StatusOK || resp.statusCode == http.StatusNotFound) {
- return resp.statusCode == http.StatusOK, nil
+ if resp != nil {
+ defer resp.body.Close()
+ if resp.statusCode == http.StatusOK || resp.statusCode == http.StatusNotFound {
+ return resp.statusCode == http.StatusOK, nil
+ }
}
return false, err
}
// DeleteContainer deletes the container with given name on the storage
-// account. See https://msdn.microsoft.com/en-us/library/azure/dd179408.aspx
-// If the container does not exist returns error.
+// account. If the container does not exist returns error.
+//
+// See https://msdn.microsoft.com/en-us/library/azure/dd179408.aspx
func (b BlobStorageClient) DeleteContainer(name string) error {
resp, err := b.deleteContainer(name)
if err != nil {
return err
}
- if resp.statusCode != http.StatusAccepted {
- return ErrNotAccepted
- }
- return nil
+ defer resp.body.Close()
+ return checkRespCode(resp.statusCode, []int{http.StatusAccepted})
}
-// DeleteContainer deletes the container with given name on the storage
-// account if it exists. See https://msdn.microsoft.com/en-us/library/azure/dd179408.aspx
-// 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.
+// DeleteContainerIfExists 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://msdn.microsoft.com/en-us/library/azure/dd179408.aspx
func (b BlobStorageClient) DeleteContainerIfExists(name string) (bool, error) {
resp, err := b.deleteContainer(name)
- if resp != nil && (resp.statusCode == http.StatusAccepted || resp.statusCode == http.StatusNotFound) {
- return resp.statusCode == http.StatusAccepted, nil
+ if resp != nil {
+ defer resp.body.Close()
+ if resp.statusCode == http.StatusAccepted || resp.statusCode == http.StatusNotFound {
+ return resp.statusCode == http.StatusAccepted, nil
+ }
}
return false, err
}
@@ -365,6 +394,7 @@ func (b BlobStorageClient) deleteContainer(name string) (*storageResponse, error
// 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://msdn.microsoft.com/en-us/library/azure/dd135734.aspx
func (b BlobStorageClient) ListBlobs(container string, params ListBlobsParameters) (BlobListResponse, error) {
q := mergeParams(params.getParameters(), url.Values{
@@ -378,60 +408,68 @@ func (b BlobStorageClient) ListBlobs(container string, params ListBlobsParameter
if err != nil {
return out, err
}
+ defer resp.body.Close()
err = xmlUnmarshal(resp.body, &out)
return out, err
}
-// BlobExists returns true if a blob with given name exists on the
-// specified container of the storage account.
+// BlobExists returns true if a blob with given name exists on the specified
+// container of the storage account.
func (b BlobStorageClient) BlobExists(container, name string) (bool, error) {
verb := "HEAD"
uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{})
headers := b.client.getStandardHeaders()
resp, err := b.client.exec(verb, uri, headers, nil)
- if resp != nil && (resp.statusCode == http.StatusOK || resp.statusCode == http.StatusNotFound) {
- return resp.statusCode == http.StatusOK, nil
+ if resp != nil {
+ defer resp.body.Close()
+ if resp.statusCode == http.StatusOK || resp.statusCode == http.StatusNotFound {
+ return resp.statusCode == http.StatusOK, nil
+ }
}
return false, err
}
-// GetBlobUrl 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 BlobStorageClient) GetBlobUrl(container, name string) string {
+// GetBlobURL 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 BlobStorageClient) GetBlobURL(container, name string) string {
if container == "" {
container = "$root"
}
return b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{})
}
-// GetBlob downloads a blob to a stream. See https://msdn.microsoft.com/en-us/library/azure/dd179440.aspx
+// GetBlob returns a stream to read the blob. Caller must call Close() the
+// reader to close on the underlying connection.
+//
+// See https://msdn.microsoft.com/en-us/library/azure/dd179440.aspx
func (b BlobStorageClient) GetBlob(container, name string) (io.ReadCloser, error) {
resp, err := b.getBlobRange(container, name, "")
if err != nil {
return nil, err
}
- if resp.statusCode != http.StatusOK {
- return nil, fmt.Errorf(errUnexpectedStatus, http.StatusOK, resp.statusCode)
+ if err := checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil {
+ return nil, err
}
return resp.body, nil
}
-// GetBlobRange 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. See https://msdn.microsoft.com/en-us/library/azure/dd179440.aspx
+// GetBlobRange 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.
+//
+// See https://msdn.microsoft.com/en-us/library/azure/dd179440.aspx
func (b BlobStorageClient) GetBlobRange(container, name, bytesRange string) (io.ReadCloser, error) {
resp, err := b.getBlobRange(container, name, bytesRange)
if err != nil {
return nil, err
}
- if resp.statusCode != http.StatusPartialContent {
- return nil, fmt.Errorf(errUnexpectedStatus, http.StatusPartialContent, resp.statusCode)
+ if err := checkRespCode(resp.statusCode, []int{http.StatusPartialContent}); err != nil {
+ return nil, err
}
return resp.body, nil
}
@@ -462,9 +500,10 @@ func (b BlobStorageClient) GetBlobProperties(container, name string) (*BlobPrope
if err != nil {
return nil, err
}
+ defer resp.body.Close()
- if resp.statusCode != http.StatusOK {
- return nil, fmt.Errorf(errUnexpectedStatus, http.StatusOK, resp.statusCode)
+ if err := checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil {
+ return nil, err
}
var contentLength int64
@@ -494,7 +533,7 @@ func (b BlobStorageClient) GetBlobProperties(container, name string) (*BlobPrope
SequenceNumber: sequenceNum,
CopyCompletionTime: resp.headers.Get("x-ms-copy-completion-time"),
CopyStatusDescription: resp.headers.Get("x-ms-copy-status-description"),
- CopyId: resp.headers.Get("x-ms-copy-id"),
+ CopyID: resp.headers.Get("x-ms-copy-id"),
CopyProgress: resp.headers.Get("x-ms-copy-progress"),
CopySource: resp.headers.Get("x-ms-copy-source"),
CopyStatus: resp.headers.Get("x-ms-copy-status"),
@@ -503,6 +542,7 @@ func (b BlobStorageClient) GetBlobProperties(container, name string) (*BlobPrope
}
// CreateBlockBlob initializes an empty block blob with no blocks.
+//
// See https://msdn.microsoft.com/en-us/library/azure/dd179451.aspx
func (b BlobStorageClient) CreateBlockBlob(container, name string) error {
path := fmt.Sprintf("%s/%s", container, name)
@@ -515,96 +555,25 @@ func (b BlobStorageClient) CreateBlockBlob(container, name string) error {
if err != nil {
return err
}
- if resp.statusCode != http.StatusCreated {
- return ErrNotCreated
- }
- return nil
-}
-
-// PutBlockBlob uploads given stream into a block blob by splitting
-// data stream into chunks and uploading as blocks. Commits the block
-// list at the end. This is a helper method built on top of PutBlock
-// and PutBlockList methods with sequential block ID counting logic.
-func (b BlobStorageClient) PutBlockBlob(container, name string, blob io.Reader) error { // TODO (ahmetalpbalkan) consider ReadCloser and closing
- return b.putBlockBlob(container, name, blob, MaxBlobBlockSize)
-}
-
-func (b BlobStorageClient) putBlockBlob(container, name string, blob io.Reader, chunkSize int) error {
- if chunkSize <= 0 || chunkSize > MaxBlobBlockSize {
- chunkSize = MaxBlobBlockSize
- }
-
- chunk := make([]byte, chunkSize)
- n, err := blob.Read(chunk)
- if err != nil && err != io.EOF {
- return err
- }
-
- if err == io.EOF {
- // Fits into one block
- return b.putSingleBlockBlob(container, name, chunk[:n])
- } else {
- // Does not fit into one block. Upload block by block then commit the block list
- blockList := []Block{}
-
- // Put blocks
- for blockNum := 0; ; blockNum++ {
- id := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%011d", blockNum)))
- data := chunk[:n]
- err = b.PutBlock(container, name, id, data)
- if err != nil {
- return err
- }
- blockList = append(blockList, Block{id, BlockStatusLatest})
-
- // Read next block
- n, err = blob.Read(chunk)
- if err != nil && err != io.EOF {
- return err
- }
- if err == io.EOF {
- break
- }
- }
-
- // Commit block list
- return b.PutBlockList(container, name, blockList)
- }
-}
-
-func (b BlobStorageClient) putSingleBlockBlob(container, name string, chunk []byte) error {
- if len(chunk) > MaxBlobBlockSize {
- return fmt.Errorf("storage: provided chunk (%d bytes) cannot fit into single-block blob (max %d bytes)", len(chunk), MaxBlobBlockSize)
- }
-
- uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{})
- headers := b.client.getStandardHeaders()
- headers["x-ms-blob-type"] = string(BlobTypeBlock)
- headers["Content-Length"] = fmt.Sprintf("%v", len(chunk))
-
- resp, err := b.client.exec("PUT", uri, headers, bytes.NewReader(chunk))
- if err != nil {
- return err
- }
- if resp.statusCode != http.StatusCreated {
- return ErrNotCreated
- }
-
- return nil
+ defer resp.body.Close()
+ return checkRespCode(resp.statusCode, []int{http.StatusCreated})
}
// PutBlock saves the given data chunk to the specified block blob with
-// given ID. See https://msdn.microsoft.com/en-us/library/azure/dd135726.aspx
-func (b BlobStorageClient) PutBlock(container, name, blockId string, chunk []byte) error {
- return b.PutBlockWithLength(container, name, blockId, uint64(len(chunk)), bytes.NewReader(chunk))
+// given ID.
+//
+// See https://msdn.microsoft.com/en-us/library/azure/dd135726.aspx
+func (b BlobStorageClient) PutBlock(container, name, blockID string, chunk []byte) error {
+ return b.PutBlockWithLength(container, name, blockID, uint64(len(chunk)), bytes.NewReader(chunk))
}
-// PutBlockWithLength saves the given data stream of exactly specified size to the block blob
-// with given ID. See https://msdn.microsoft.com/en-us/library/azure/dd135726.aspx
-// It is an alternative to PutBlocks where data comes as stream but the length is
-// known in advance.
-func (b BlobStorageClient) PutBlockWithLength(container, name, blockId string, size uint64, blob io.Reader) error {
- uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{"comp": {"block"}, "blockid": {blockId}})
+// 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.
+//
+// See https://msdn.microsoft.com/en-us/library/azure/dd135726.aspx
+func (b BlobStorageClient) PutBlockWithLength(container, name, blockID string, size uint64, blob io.Reader) error {
+ uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{"comp": {"block"}, "blockid": {blockID}})
headers := b.client.getStandardHeaders()
headers["x-ms-blob-type"] = string(BlobTypeBlock)
headers["Content-Length"] = fmt.Sprintf("%v", size)
@@ -613,34 +582,31 @@ func (b BlobStorageClient) PutBlockWithLength(container, name, blockId string, s
if err != nil {
return err
}
- if resp.statusCode != http.StatusCreated {
- return ErrNotCreated
- }
-
- return nil
+ defer resp.body.Close()
+ return checkRespCode(resp.statusCode, []int{http.StatusCreated})
}
-// PutBlockList saves list of blocks to the specified block blob. See
-// https://msdn.microsoft.com/en-us/library/azure/dd179467.aspx
+// PutBlockList saves list of blocks to the specified block blob.
+//
+// See https://msdn.microsoft.com/en-us/library/azure/dd179467.aspx
func (b BlobStorageClient) PutBlockList(container, name string, blocks []Block) error {
- blockListXml := prepareBlockListRequest(blocks)
+ blockListXML := prepareBlockListRequest(blocks)
uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{"comp": {"blocklist"}})
headers := b.client.getStandardHeaders()
- headers["Content-Length"] = fmt.Sprintf("%v", len(blockListXml))
+ headers["Content-Length"] = fmt.Sprintf("%v", len(blockListXML))
- resp, err := b.client.exec("PUT", uri, headers, strings.NewReader(blockListXml))
+ resp, err := b.client.exec("PUT", uri, headers, strings.NewReader(blockListXML))
if err != nil {
return err
}
- if resp.statusCode != http.StatusCreated {
- return ErrNotCreated
- }
- return nil
+ defer resp.body.Close()
+ return checkRespCode(resp.statusCode, []int{http.StatusCreated})
}
-// GetBlockList retrieves list of blocks in the specified block blob. See
-// https://msdn.microsoft.com/en-us/library/azure/dd179400.aspx
+// GetBlockList retrieves list of blocks in the specified block blob.
+//
+// See https://msdn.microsoft.com/en-us/library/azure/dd179400.aspx
func (b BlobStorageClient) GetBlockList(container, name string, blockType BlockListType) (BlockListResponse, error) {
params := url.Values{"comp": {"blocklist"}, "blocklisttype": {string(blockType)}}
uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), params)
@@ -651,6 +617,7 @@ func (b BlobStorageClient) GetBlockList(container, name string, blockType BlockL
if err != nil {
return out, err
}
+ defer resp.body.Close()
err = xmlUnmarshal(resp.body, &out)
return out, err
@@ -659,6 +626,7 @@ func (b BlobStorageClient) GetBlockList(container, name string, blockType BlockL
// 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 https://msdn.microsoft.com/en-us/library/azure/dd179451.aspx
func (b BlobStorageClient) PutPageBlob(container, name string, size int64) error {
path := fmt.Sprintf("%s/%s", container, name)
@@ -672,15 +640,15 @@ func (b BlobStorageClient) PutPageBlob(container, name string, size int64) error
if err != nil {
return err
}
- if resp.statusCode != http.StatusCreated {
- return ErrNotCreated
- }
- return nil
+ defer resp.body.Close()
+
+ return checkRespCode(resp.statusCode, []int{http.StatusCreated})
}
// PutPage writes a range of pages to a page blob or clears the given range.
// In case of 'clear' writes, given chunk is discarded. Ranges must be aligned
// with 512-byte boundaries and chunk must be of size multiplies by 512.
+//
// See https://msdn.microsoft.com/en-us/library/ee691975.aspx
func (b BlobStorageClient) PutPage(container, name string, startByte, endByte int64, writeType PageWriteType, chunk []byte) error {
path := fmt.Sprintf("%s/%s", container, name)
@@ -705,13 +673,13 @@ func (b BlobStorageClient) PutPage(container, name string, startByte, endByte in
if err != nil {
return err
}
- if resp.statusCode != http.StatusCreated {
- return ErrNotCreated
- }
- return nil
+ defer resp.body.Close()
+
+ return checkRespCode(resp.statusCode, []int{http.StatusCreated})
}
// GetPageRanges returns the list of valid page ranges for a page blob.
+//
// See https://msdn.microsoft.com/en-us/library/azure/ee691973.aspx
func (b BlobStorageClient) GetPageRanges(container, name string) (GetPageRangesResponse, error) {
path := fmt.Sprintf("%s/%s", container, name)
@@ -723,26 +691,28 @@ func (b BlobStorageClient) GetPageRanges(container, name string) (GetPageRangesR
if err != nil {
return out, err
}
+ defer resp.body.Close()
- if resp.statusCode != http.StatusOK {
- return out, fmt.Errorf(errUnexpectedStatus, http.StatusOK, resp.statusCode)
+ if err := checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil {
+ return out, err
}
-
err = xmlUnmarshal(resp.body, &out)
return out, err
}
-// CopyBlob 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
-// GetBlobURL method.) There is no SLA on blob copy and therefore this helper
-// method works faster on smaller files. See https://msdn.microsoft.com/en-us/library/azure/dd894037.aspx
+// CopyBlob 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 GetBlobURL method.) There is no SLA on blob copy and therefore
+// this helper method works faster on smaller files.
+//
+// See https://msdn.microsoft.com/en-us/library/azure/dd894037.aspx
func (b BlobStorageClient) CopyBlob(container, name, sourceBlob string) error {
- copyId, err := b.startBlobCopy(container, name, sourceBlob)
+ copyID, err := b.startBlobCopy(container, name, sourceBlob)
if err != nil {
return err
}
- return b.waitForBlobCopy(container, name, copyId)
+ return b.waitForBlobCopy(container, name, copyID)
}
func (b BlobStorageClient) startBlobCopy(container, name, sourceBlob string) (string, error) {
@@ -756,26 +726,28 @@ func (b BlobStorageClient) startBlobCopy(container, name, sourceBlob string) (st
if err != nil {
return "", err
}
- if resp.statusCode != http.StatusAccepted && resp.statusCode != http.StatusCreated {
- return "", fmt.Errorf(errUnexpectedStatus, []int{http.StatusAccepted, http.StatusCreated}, resp.statusCode)
+ defer resp.body.Close()
+
+ if err := checkRespCode(resp.statusCode, []int{http.StatusAccepted, http.StatusCreated}); err != nil {
+ return "", err
}
- copyId := resp.headers.Get("x-ms-copy-id")
- if copyId == "" {
+ copyID := resp.headers.Get("x-ms-copy-id")
+ if copyID == "" {
return "", errors.New("Got empty copy id header")
}
- return copyId, nil
+ return copyID, nil
}
-func (b BlobStorageClient) waitForBlobCopy(container, name, copyId string) error {
+func (b BlobStorageClient) waitForBlobCopy(container, name, copyID string) error {
for {
props, err := b.GetBlobProperties(container, name)
if err != nil {
return err
}
- if props.CopyId != copyId {
- return errBlobCopyIdMismatch
+ if props.CopyID != copyID {
+ return errBlobCopyIDMismatch
}
switch props.CopyStatus {
@@ -786,7 +758,7 @@ func (b BlobStorageClient) waitForBlobCopy(container, name, copyId string) error
case blobCopyStatusAborted:
return errBlobCopyAborted
case blobCopyStatusFailed:
- return fmt.Errorf("storage: blob copy failed. Id=%s Description=%s", props.CopyId, props.CopyStatusDescription)
+ return fmt.Errorf("storage: blob copy failed. Id=%s Description=%s", props.CopyID, props.CopyStatusDescription)
default:
return fmt.Errorf("storage: unhandled blob copy status: '%s'", props.CopyStatus)
}
@@ -801,20 +773,20 @@ func (b BlobStorageClient) DeleteBlob(container, name string) error {
if err != nil {
return err
}
- if resp.statusCode != http.StatusAccepted {
- return ErrNotAccepted
- }
- return nil
+ defer resp.body.Close()
+ return checkRespCode(resp.statusCode, []int{http.StatusAccepted})
}
-// DeleteBlobIfExists deletes the given blob from the specified container
-// If the blob is deleted with this call, returns true. Otherwise returns
-// false. See https://msdn.microsoft.com/en-us/library/azure/dd179413.aspx
+// DeleteBlobIfExists deletes the given blob from the specified container If the
+// blob is deleted with this call, returns true. Otherwise returns false.
+//
+// See https://msdn.microsoft.com/en-us/library/azure/dd179413.aspx
func (b BlobStorageClient) DeleteBlobIfExists(container, name string) (bool, error) {
resp, err := b.deleteBlob(container, name)
if resp != nil && (resp.statusCode == http.StatusAccepted || resp.statusCode == http.StatusNotFound) {
return resp.statusCode == http.StatusAccepted, nil
}
+ defer resp.body.Close()
return false, err
}
@@ -831,19 +803,22 @@ func pathForContainer(name string) string {
return fmt.Sprintf("/%s", name)
}
-// helper method to construct the path to a blob given its container and blob name
+// helper method to construct the path to a blob given its container and blob
+// name
func pathForBlob(container, name string) string {
return fmt.Sprintf("/%s/%s", container, name)
}
-// GetBlobSASURI creates an URL to the specified blob which contains the Shared Access Signature
-// with specified permissions and expiration time. See https://msdn.microsoft.com/en-us/library/azure/ee395415.aspx
+// GetBlobSASURI creates an URL to the specified blob which contains the Shared
+// Access Signature with specified permissions and expiration time.
+//
+// See https://msdn.microsoft.com/en-us/library/azure/ee395415.aspx
func (b BlobStorageClient) GetBlobSASURI(container, name string, expiry time.Time, permissions string) (string, error) {
var (
signedPermissions = permissions
- blobUrl = b.GetBlobUrl(container, name)
+ blobURL = b.GetBlobURL(container, name)
)
- canonicalizedResource, err := b.client.buildCanonicalizedResource(blobUrl)
+ canonicalizedResource, err := b.client.buildCanonicalizedResource(blobURL)
if err != nil {
return "", err
}
@@ -864,12 +839,12 @@ func (b BlobStorageClient) GetBlobSASURI(container, name string, expiry time.Tim
"sig": {sig},
}
- sasUrl, err := url.Parse(blobUrl)
+ sasURL, err := url.Parse(blobURL)
if err != nil {
return "", err
}
- sasUrl.RawQuery = sasParams.Encode()
- return sasUrl.String(), nil
+ sasURL.RawQuery = sasParams.Encode()
+ return sasURL.String(), nil
}
func blobSASStringToSign(signedVersion, canonicalizedResource, signedExpiry, signedPermissions string) (string, error) {
@@ -878,7 +853,6 @@ func blobSASStringToSign(signedVersion, canonicalizedResource, signedExpiry, sig
// 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
- } else {
- return "", errors.New("storage: not implemented SAS for versions earlier than 2013-08-15")
}
+ return "", errors.New("storage: not implemented SAS for versions earlier than 2013-08-15")
}
diff --git a/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/blob_test.go b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/blob_test.go
new file mode 100644
index 000000000..14a2f6b2c
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/blob_test.go
@@ -0,0 +1,625 @@
+package storage
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/base64"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "sort"
+ "sync"
+ "testing"
+ "time"
+
+ chk "gopkg.in/check.v1"
+)
+
+type StorageBlobSuite struct{}
+
+var _ = chk.Suite(&StorageBlobSuite{})
+
+const testContainerPrefix = "zzzztest-"
+
+func getBlobClient(c *chk.C) BlobStorageClient {
+ return getBasicClient(c).GetBlobService()
+}
+
+func (s *StorageBlobSuite) Test_pathForContainer(c *chk.C) {
+ c.Assert(pathForContainer("foo"), chk.Equals, "/foo")
+}
+
+func (s *StorageBlobSuite) Test_pathForBlob(c *chk.C) {
+ c.Assert(pathForBlob("foo", "blob"), chk.Equals, "/foo/blob")
+}
+
+func (s *StorageBlobSuite) Test_blobSASStringToSign(c *chk.C) {
+ _, err := blobSASStringToSign("2012-02-12", "CS", "SE", "SP")
+ c.Assert(err, chk.NotNil) // not implemented SAS for versions earlier than 2013-08-15
+
+ out, err := blobSASStringToSign("2013-08-15", "CS", "SE", "SP")
+ c.Assert(err, chk.IsNil)
+ c.Assert(out, chk.Equals, "SP\n\nSE\nCS\n\n2013-08-15\n\n\n\n\n")
+}
+
+func (s *StorageBlobSuite) TestGetBlobSASURI(c *chk.C) {
+ api, err := NewClient("foo", "YmFy", DefaultBaseURL, "2013-08-15", true)
+ c.Assert(err, chk.IsNil)
+ cli := api.GetBlobService()
+ expiry := time.Time{}
+
+ expectedParts := url.URL{
+ Scheme: "https",
+ Host: "foo.blob.core.windows.net",
+ Path: "container/name",
+ RawQuery: url.Values{
+ "sv": {"2013-08-15"},
+ "sig": {"/OXG7rWh08jYwtU03GzJM0DHZtidRGpC6g69rSGm3I0="},
+ "sr": {"b"},
+ "sp": {"r"},
+ "se": {"0001-01-01T00:00:00Z"},
+ }.Encode()}
+
+ u, err := cli.GetBlobSASURI("container", "name", expiry, "r")
+ c.Assert(err, chk.IsNil)
+ sasParts, err := url.Parse(u)
+ c.Assert(err, chk.IsNil)
+ c.Assert(expectedParts.String(), chk.Equals, sasParts.String())
+ c.Assert(expectedParts.Query(), chk.DeepEquals, sasParts.Query())
+}
+
+func (s *StorageBlobSuite) TestBlobSASURICorrectness(c *chk.C) {
+ cli := getBlobClient(c)
+ cnt := randContainer()
+ blob := randString(20)
+ body := []byte(randString(100))
+ expiry := time.Now().UTC().Add(time.Hour)
+ permissions := "r"
+
+ c.Assert(cli.CreateContainer(cnt, ContainerAccessTypePrivate), chk.IsNil)
+ defer cli.DeleteContainer(cnt)
+
+ c.Assert(cli.putSingleBlockBlob(cnt, blob, body), chk.IsNil)
+
+ sasURI, err := cli.GetBlobSASURI(cnt, blob, expiry, permissions)
+ c.Assert(err, chk.IsNil)
+
+ resp, err := http.Get(sasURI)
+ c.Assert(err, chk.IsNil)
+
+ blobResp, err := ioutil.ReadAll(resp.Body)
+ defer resp.Body.Close()
+ c.Assert(err, chk.IsNil)
+
+ c.Assert(resp.StatusCode, chk.Equals, http.StatusOK)
+ c.Assert(len(blobResp), chk.Equals, len(body))
+}
+
+func (s *StorageBlobSuite) TestListContainersPagination(c *chk.C) {
+ cli := getBlobClient(c)
+ c.Assert(deleteTestContainers(cli), chk.IsNil)
+
+ const n = 5
+ const pageSize = 2
+
+ // Create test containers
+ created := []string{}
+ for i := 0; i < n; i++ {
+ name := randContainer()
+ c.Assert(cli.CreateContainer(name, ContainerAccessTypePrivate), chk.IsNil)
+ created = append(created, name)
+ }
+ sort.Strings(created)
+
+ // Defer test container deletions
+ defer func() {
+ var wg sync.WaitGroup
+ for _, cnt := range created {
+ wg.Add(1)
+ go func(name string) {
+ c.Assert(cli.DeleteContainer(name), chk.IsNil)
+ wg.Done()
+ }(cnt)
+ }
+ wg.Wait()
+ }()
+
+ // Paginate results
+ seen := []string{}
+ marker := ""
+ for {
+ resp, err := cli.ListContainers(ListContainersParameters{
+ Prefix: testContainerPrefix,
+ MaxResults: pageSize,
+ Marker: marker})
+ c.Assert(err, chk.IsNil)
+
+ containers := resp.Containers
+ if len(containers) > pageSize {
+ c.Fatalf("Got a bigger page. Expected: %d, got: %d", pageSize, len(containers))
+ }
+
+ for _, c := range containers {
+ seen = append(seen, c.Name)
+ }
+
+ marker = resp.NextMarker
+ if marker == "" || len(containers) == 0 {
+ break
+ }
+ }
+
+ c.Assert(seen, chk.DeepEquals, created)
+}
+
+func (s *StorageBlobSuite) TestContainerExists(c *chk.C) {
+ cnt := randContainer()
+ cli := getBlobClient(c)
+ ok, err := cli.ContainerExists(cnt)
+ c.Assert(err, chk.IsNil)
+ c.Assert(ok, chk.Equals, false)
+
+ c.Assert(cli.CreateContainer(cnt, ContainerAccessTypeBlob), chk.IsNil)
+ defer cli.DeleteContainer(cnt)
+
+ ok, err = cli.ContainerExists(cnt)
+ c.Assert(err, chk.IsNil)
+ c.Assert(ok, chk.Equals, true)
+}
+
+func (s *StorageBlobSuite) TestCreateDeleteContainer(c *chk.C) {
+ cnt := randContainer()
+ cli := getBlobClient(c)
+ c.Assert(cli.CreateContainer(cnt, ContainerAccessTypePrivate), chk.IsNil)
+ c.Assert(cli.DeleteContainer(cnt), chk.IsNil)
+}
+
+func (s *StorageBlobSuite) TestCreateContainerIfNotExists(c *chk.C) {
+ cnt := randContainer()
+ cli := getBlobClient(c)
+
+ // First create
+ ok, err := cli.CreateContainerIfNotExists(cnt, ContainerAccessTypePrivate)
+ c.Assert(err, chk.IsNil)
+ c.Assert(ok, chk.Equals, true)
+
+ // Second create, should not give errors
+ ok, err = cli.CreateContainerIfNotExists(cnt, ContainerAccessTypePrivate)
+ c.Assert(err, chk.IsNil)
+ defer cli.DeleteContainer(cnt)
+ c.Assert(ok, chk.Equals, false)
+}
+
+func (s *StorageBlobSuite) TestDeleteContainerIfExists(c *chk.C) {
+ cnt := randContainer()
+ cli := getBlobClient(c)
+
+ // Nonexisting container
+ c.Assert(cli.DeleteContainer(cnt), chk.NotNil)
+
+ ok, err := cli.DeleteContainerIfExists(cnt)
+ c.Assert(err, chk.IsNil)
+ c.Assert(ok, chk.Equals, false)
+
+ // Existing container
+ c.Assert(cli.CreateContainer(cnt, ContainerAccessTypePrivate), chk.IsNil)
+ ok, err = cli.DeleteContainerIfExists(cnt)
+ c.Assert(err, chk.IsNil)
+ c.Assert(ok, chk.Equals, true)
+}
+
+func (s *StorageBlobSuite) TestBlobExists(c *chk.C) {
+ cnt := randContainer()
+ blob := randString(20)
+ cli := getBlobClient(c)
+
+ c.Assert(cli.CreateContainer(cnt, ContainerAccessTypeBlob), chk.IsNil)
+ defer cli.DeleteContainer(cnt)
+ c.Assert(cli.putSingleBlockBlob(cnt, blob, []byte("Hello!")), chk.IsNil)
+ defer cli.DeleteBlob(cnt, blob)
+
+ ok, err := cli.BlobExists(cnt, blob+".foo")
+ c.Assert(err, chk.IsNil)
+ c.Assert(ok, chk.Equals, false)
+
+ ok, err = cli.BlobExists(cnt, blob)
+ c.Assert(err, chk.IsNil)
+ c.Assert(ok, chk.Equals, true)
+}
+
+func (s *StorageBlobSuite) TestGetBlobURL(c *chk.C) {
+ api, err := NewBasicClient("foo", "YmFy")
+ c.Assert(err, chk.IsNil)
+ cli := api.GetBlobService()
+
+ c.Assert(cli.GetBlobURL("c", "nested/blob"), chk.Equals, "https://foo.blob.core.windows.net/c/nested/blob")
+ c.Assert(cli.GetBlobURL("", "blob"), chk.Equals, "https://foo.blob.core.windows.net/$root/blob")
+ c.Assert(cli.GetBlobURL("", "nested/blob"), chk.Equals, "https://foo.blob.core.windows.net/$root/nested/blob")
+}
+
+func (s *StorageBlobSuite) TestBlobCopy(c *chk.C) {
+ if testing.Short() {
+ c.Skip("skipping blob copy in short mode, no SLA on async operation")
+ }
+
+ cli := getBlobClient(c)
+ cnt := randContainer()
+ src := randString(20)
+ dst := randString(20)
+ body := []byte(randString(1024))
+
+ c.Assert(cli.CreateContainer(cnt, ContainerAccessTypePrivate), chk.IsNil)
+ defer cli.deleteContainer(cnt)
+
+ c.Assert(cli.putSingleBlockBlob(cnt, src, body), chk.IsNil)
+ defer cli.DeleteBlob(cnt, src)
+
+ c.Assert(cli.CopyBlob(cnt, dst, cli.GetBlobURL(cnt, src)), chk.IsNil)
+ defer cli.DeleteBlob(cnt, dst)
+
+ blobBody, err := cli.GetBlob(cnt, dst)
+ c.Assert(err, chk.IsNil)
+
+ b, err := ioutil.ReadAll(blobBody)
+ defer blobBody.Close()
+ c.Assert(err, chk.IsNil)
+ c.Assert(b, chk.DeepEquals, body)
+}
+
+func (s *StorageBlobSuite) TestDeleteBlobIfExists(c *chk.C) {
+ cnt := randContainer()
+ blob := randString(20)
+
+ cli := getBlobClient(c)
+ c.Assert(cli.DeleteBlob(cnt, blob), chk.NotNil)
+
+ ok, err := cli.DeleteBlobIfExists(cnt, blob)
+ c.Assert(err, chk.IsNil)
+ c.Assert(ok, chk.Equals, false)
+}
+
+func (s *StorageBlobSuite) TestGetBlobProperties(c *chk.C) {
+ cnt := randContainer()
+ blob := randString(20)
+ contents := randString(64)
+
+ cli := getBlobClient(c)
+ c.Assert(cli.CreateContainer(cnt, ContainerAccessTypePrivate), chk.IsNil)
+ defer cli.DeleteContainer(cnt)
+
+ // Nonexisting blob
+ _, err := cli.GetBlobProperties(cnt, blob)
+ c.Assert(err, chk.NotNil)
+
+ // Put the blob
+ c.Assert(cli.putSingleBlockBlob(cnt, blob, []byte(contents)), chk.IsNil)
+
+ // Get blob properties
+ props, err := cli.GetBlobProperties(cnt, blob)
+ c.Assert(err, chk.IsNil)
+
+ c.Assert(props.ContentLength, chk.Equals, int64(len(contents)))
+ c.Assert(props.BlobType, chk.Equals, BlobTypeBlock)
+}
+
+func (s *StorageBlobSuite) TestListBlobsPagination(c *chk.C) {
+ cli := getBlobClient(c)
+ cnt := randContainer()
+
+ c.Assert(cli.CreateContainer(cnt, ContainerAccessTypePrivate), chk.IsNil)
+ defer cli.DeleteContainer(cnt)
+
+ blobs := []string{}
+ const n = 5
+ const pageSize = 2
+ for i := 0; i < n; i++ {
+ name := randString(20)
+ c.Assert(cli.putSingleBlockBlob(cnt, name, []byte("Hello, world!")), chk.IsNil)
+ blobs = append(blobs, name)
+ }
+ sort.Strings(blobs)
+
+ // Paginate
+ seen := []string{}
+ marker := ""
+ for {
+ resp, err := cli.ListBlobs(cnt, ListBlobsParameters{
+ MaxResults: pageSize,
+ Marker: marker})
+ c.Assert(err, chk.IsNil)
+
+ for _, v := range resp.Blobs {
+ seen = append(seen, v.Name)
+ }
+
+ marker = resp.NextMarker
+ if marker == "" || len(resp.Blobs) == 0 {
+ break
+ }
+ }
+
+ // Compare
+ c.Assert(seen, chk.DeepEquals, blobs)
+}
+
+func (s *StorageBlobSuite) TestPutEmptyBlockBlob(c *chk.C) {
+ cli := getBlobClient(c)
+ cnt := randContainer()
+
+ c.Assert(cli.CreateContainer(cnt, ContainerAccessTypePrivate), chk.IsNil)
+ defer cli.deleteContainer(cnt)
+
+ blob := randString(20)
+ c.Assert(cli.putSingleBlockBlob(cnt, blob, []byte{}), chk.IsNil)
+
+ props, err := cli.GetBlobProperties(cnt, blob)
+ c.Assert(err, chk.IsNil)
+ c.Assert(props.ContentLength, chk.Not(chk.Equals), 0)
+}
+
+func (s *StorageBlobSuite) TestGetBlobRange(c *chk.C) {
+ cnt := randContainer()
+ blob := randString(20)
+ body := "0123456789"
+
+ cli := getBlobClient(c)
+ c.Assert(cli.CreateContainer(cnt, ContainerAccessTypeBlob), chk.IsNil)
+ defer cli.DeleteContainer(cnt)
+
+ c.Assert(cli.putSingleBlockBlob(cnt, blob, []byte(body)), chk.IsNil)
+ defer cli.DeleteBlob(cnt, blob)
+
+ // Read 1-3
+ for _, r := range []struct {
+ rangeStr string
+ expected string
+ }{
+ {"0-", body},
+ {"1-3", body[1 : 3+1]},
+ {"3-", body[3:]},
+ } {
+ resp, err := cli.GetBlobRange(cnt, blob, r.rangeStr)
+ c.Assert(err, chk.IsNil)
+ blobBody, err := ioutil.ReadAll(resp)
+ c.Assert(err, chk.IsNil)
+
+ str := string(blobBody)
+ c.Assert(str, chk.Equals, r.expected)
+ }
+}
+
+func (s *StorageBlobSuite) TestPutBlock(c *chk.C) {
+ cli := getBlobClient(c)
+ cnt := randContainer()
+ c.Assert(cli.CreateContainer(cnt, ContainerAccessTypePrivate), chk.IsNil)
+ defer cli.deleteContainer(cnt)
+
+ blob := randString(20)
+ chunk := []byte(randString(1024))
+ blockID := base64.StdEncoding.EncodeToString([]byte("foo"))
+ c.Assert(cli.PutBlock(cnt, blob, blockID, chunk), chk.IsNil)
+}
+
+func (s *StorageBlobSuite) TestGetBlockList_PutBlockList(c *chk.C) {
+ cli := getBlobClient(c)
+ cnt := randContainer()
+ c.Assert(cli.CreateContainer(cnt, ContainerAccessTypePrivate), chk.IsNil)
+ defer cli.deleteContainer(cnt)
+
+ blob := randString(20)
+ chunk := []byte(randString(1024))
+ blockID := base64.StdEncoding.EncodeToString([]byte("foo"))
+
+ // Put one block
+ c.Assert(cli.PutBlock(cnt, blob, blockID, chunk), chk.IsNil)
+ defer cli.deleteBlob(cnt, blob)
+
+ // Get committed blocks
+ committed, err := cli.GetBlockList(cnt, blob, BlockListTypeCommitted)
+ c.Assert(err, chk.IsNil)
+
+ if len(committed.CommittedBlocks) > 0 {
+ c.Fatal("There are committed blocks")
+ }
+
+ // Get uncommitted blocks
+ uncommitted, err := cli.GetBlockList(cnt, blob, BlockListTypeUncommitted)
+ c.Assert(err, chk.IsNil)
+
+ c.Assert(len(uncommitted.UncommittedBlocks), chk.Equals, 1)
+ // Commit block list
+ c.Assert(cli.PutBlockList(cnt, blob, []Block{{blockID, BlockStatusUncommitted}}), chk.IsNil)
+
+ // Get all blocks
+ all, err := cli.GetBlockList(cnt, blob, BlockListTypeAll)
+ c.Assert(err, chk.IsNil)
+ c.Assert(len(all.CommittedBlocks), chk.Equals, 1)
+ c.Assert(len(all.UncommittedBlocks), chk.Equals, 0)
+
+ // Verify the block
+ thatBlock := all.CommittedBlocks[0]
+ c.Assert(thatBlock.Name, chk.Equals, blockID)
+ c.Assert(thatBlock.Size, chk.Equals, int64(len(chunk)))
+}
+
+func (s *StorageBlobSuite) TestCreateBlockBlob(c *chk.C) {
+ cli := getBlobClient(c)
+ cnt := randContainer()
+ c.Assert(cli.CreateContainer(cnt, ContainerAccessTypePrivate), chk.IsNil)
+ defer cli.deleteContainer(cnt)
+
+ blob := randString(20)
+ c.Assert(cli.CreateBlockBlob(cnt, blob), chk.IsNil)
+
+ // Verify
+ blocks, err := cli.GetBlockList(cnt, blob, BlockListTypeAll)
+ c.Assert(err, chk.IsNil)
+ c.Assert(len(blocks.CommittedBlocks), chk.Equals, 0)
+ c.Assert(len(blocks.UncommittedBlocks), chk.Equals, 0)
+}
+
+func (s *StorageBlobSuite) TestPutPageBlob(c *chk.C) {
+ cli := getBlobClient(c)
+ cnt := randContainer()
+ c.Assert(cli.CreateContainer(cnt, ContainerAccessTypePrivate), chk.IsNil)
+ defer cli.deleteContainer(cnt)
+
+ blob := randString(20)
+ size := int64(10 * 1024 * 1024)
+ c.Assert(cli.PutPageBlob(cnt, blob, size), chk.IsNil)
+
+ // Verify
+ props, err := cli.GetBlobProperties(cnt, blob)
+ c.Assert(err, chk.IsNil)
+ c.Assert(props.ContentLength, chk.Equals, size)
+ c.Assert(props.BlobType, chk.Equals, BlobTypePage)
+}
+
+func (s *StorageBlobSuite) TestPutPagesUpdate(c *chk.C) {
+ cli := getBlobClient(c)
+ cnt := randContainer()
+ c.Assert(cli.CreateContainer(cnt, ContainerAccessTypePrivate), chk.IsNil)
+ defer cli.deleteContainer(cnt)
+
+ blob := randString(20)
+ size := int64(10 * 1024 * 1024) // larger than we'll use
+ c.Assert(cli.PutPageBlob(cnt, blob, size), chk.IsNil)
+
+ chunk1 := []byte(randString(1024))
+ chunk2 := []byte(randString(512))
+
+ // Append chunks
+ c.Assert(cli.PutPage(cnt, blob, 0, int64(len(chunk1)-1), PageWriteTypeUpdate, chunk1), chk.IsNil)
+ c.Assert(cli.PutPage(cnt, blob, int64(len(chunk1)), int64(len(chunk1)+len(chunk2)-1), PageWriteTypeUpdate, chunk2), chk.IsNil)
+
+ // Verify contents
+ out, err := cli.GetBlobRange(cnt, blob, fmt.Sprintf("%v-%v", 0, len(chunk1)+len(chunk2)-1))
+ c.Assert(err, chk.IsNil)
+ defer out.Close()
+ blobContents, err := ioutil.ReadAll(out)
+ c.Assert(err, chk.IsNil)
+ c.Assert(blobContents, chk.DeepEquals, append(chunk1, chunk2...))
+ out.Close()
+
+ // Overwrite first half of chunk1
+ chunk0 := []byte(randString(512))
+ c.Assert(cli.PutPage(cnt, blob, 0, int64(len(chunk0)-1), PageWriteTypeUpdate, chunk0), chk.IsNil)
+
+ // Verify contents
+ out, err = cli.GetBlobRange(cnt, blob, fmt.Sprintf("%v-%v", 0, len(chunk1)+len(chunk2)-1))
+ c.Assert(err, chk.IsNil)
+ defer out.Close()
+ blobContents, err = ioutil.ReadAll(out)
+ c.Assert(err, chk.IsNil)
+ c.Assert(blobContents, chk.DeepEquals, append(append(chunk0, chunk1[512:]...), chunk2...))
+}
+
+func (s *StorageBlobSuite) TestPutPagesClear(c *chk.C) {
+ cli := getBlobClient(c)
+ cnt := randContainer()
+ c.Assert(cli.CreateContainer(cnt, ContainerAccessTypePrivate), chk.IsNil)
+ defer cli.deleteContainer(cnt)
+
+ blob := randString(20)
+ size := int64(10 * 1024 * 1024) // larger than we'll use
+ c.Assert(cli.PutPageBlob(cnt, blob, size), chk.IsNil)
+
+ // Put 0-2047
+ chunk := []byte(randString(2048))
+ c.Assert(cli.PutPage(cnt, blob, 0, 2047, PageWriteTypeUpdate, chunk), chk.IsNil)
+
+ // Clear 512-1023
+ c.Assert(cli.PutPage(cnt, blob, 512, 1023, PageWriteTypeClear, nil), chk.IsNil)
+
+ // Verify contents
+ out, err := cli.GetBlobRange(cnt, blob, "0-2047")
+ c.Assert(err, chk.IsNil)
+ contents, err := ioutil.ReadAll(out)
+ c.Assert(err, chk.IsNil)
+ defer out.Close()
+ c.Assert(contents, chk.DeepEquals, append(append(chunk[:512], make([]byte, 512)...), chunk[1024:]...))
+}
+
+func (s *StorageBlobSuite) TestGetPageRanges(c *chk.C) {
+ cli := getBlobClient(c)
+ cnt := randContainer()
+ c.Assert(cli.CreateContainer(cnt, ContainerAccessTypePrivate), chk.IsNil)
+ defer cli.deleteContainer(cnt)
+
+ blob := randString(20)
+ size := int64(10 * 1024 * 1024) // larger than we'll use
+ c.Assert(cli.PutPageBlob(cnt, blob, size), chk.IsNil)
+
+ // Get page ranges on empty blob
+ out, err := cli.GetPageRanges(cnt, blob)
+ c.Assert(err, chk.IsNil)
+ c.Assert(len(out.PageList), chk.Equals, 0)
+
+ // Add 0-512 page
+ c.Assert(cli.PutPage(cnt, blob, 0, 511, PageWriteTypeUpdate, []byte(randString(512))), chk.IsNil)
+
+ out, err = cli.GetPageRanges(cnt, blob)
+ c.Assert(err, chk.IsNil)
+ c.Assert(len(out.PageList), chk.Equals, 1)
+
+ // Add 1024-2048
+ c.Assert(cli.PutPage(cnt, blob, 1024, 2047, PageWriteTypeUpdate, []byte(randString(1024))), chk.IsNil)
+
+ out, err = cli.GetPageRanges(cnt, blob)
+ c.Assert(err, chk.IsNil)
+ c.Assert(len(out.PageList), chk.Equals, 2)
+}
+
+func deleteTestContainers(cli BlobStorageClient) error {
+ for {
+ resp, err := cli.ListContainers(ListContainersParameters{Prefix: testContainerPrefix})
+ if err != nil {
+ return err
+ }
+ if len(resp.Containers) == 0 {
+ break
+ }
+ for _, c := range resp.Containers {
+ err = cli.DeleteContainer(c.Name)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func (b BlobStorageClient) putSingleBlockBlob(container, name string, chunk []byte) error {
+ if len(chunk) > MaxBlobBlockSize {
+ return fmt.Errorf("storage: provided chunk (%d bytes) cannot fit into single-block blob (max %d bytes)", len(chunk), MaxBlobBlockSize)
+ }
+
+ uri := b.client.getEndpoint(blobServiceName, pathForBlob(container, name), url.Values{})
+ headers := b.client.getStandardHeaders()
+ headers["x-ms-blob-type"] = string(BlobTypeBlock)
+ headers["Content-Length"] = fmt.Sprintf("%v", len(chunk))
+
+ resp, err := b.client.exec("PUT", uri, headers, bytes.NewReader(chunk))
+ if err != nil {
+ return err
+ }
+ return checkRespCode(resp.statusCode, []int{http.StatusCreated})
+}
+
+func randContainer() string {
+ return testContainerPrefix + randString(32-len(testContainerPrefix))
+}
+
+func randString(n int) string {
+ if n <= 0 {
+ panic("negative number")
+ }
+ const alphanum = "0123456789abcdefghijklmnopqrstuvwxyz"
+ var bytes = make([]byte, n)
+ rand.Read(bytes)
+ for i, b := range bytes {
+ bytes[i] = alphanum[b%byte(len(alphanum))]
+ }
+ return string(bytes)
+}
diff --git a/Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/storage/client.go b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/client.go
similarity index 59%
rename from Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/storage/client.go
rename to Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/client.go
index 2928dbab3..6c171050a 100644
--- a/Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/storage/client.go
+++ b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/client.go
@@ -1,3 +1,4 @@
+// Package storage provides clients for Microsoft Azure Storage Services.
package storage
import (
@@ -15,22 +16,28 @@ import (
)
const (
- DefaultBaseUrl = "core.windows.net"
- DefaultApiVersion = "2014-02-14"
- defaultUseHttps = true
+ // DefaultBaseURL is the domain name used for storage requests 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 = "2014-02-14"
+
+ defaultUseHTTPS = true
blobServiceName = "blob"
tableServiceName = "table"
queueServiceName = "queue"
)
-// StorageClient is the object that needs to be constructed
-// to perform operations on the storage account.
-type StorageClient struct {
+// Client is the object that needs to be constructed to perform
+// operations on the storage account.
+type Client struct {
accountName string
accountKey []byte
- useHttps bool
- baseUrl string
+ useHTTPS bool
+ baseURL string
apiVersion string
}
@@ -40,10 +47,10 @@ type storageResponse struct {
body io.ReadCloser
}
-// StorageServiceError contains fields of the error response from
+// 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 StorageServiceError struct {
+type AzureStorageServiceError struct {
Code string `xml:"Code"`
Message string `xml:"Message"`
AuthenticationErrorDetail string `xml:"AuthenticationErrorDetail"`
@@ -51,25 +58,43 @@ type StorageServiceError struct {
QueryParameterValue string `xml:"QueryParameterValue"`
Reason string `xml:"Reason"`
StatusCode int
- RequestId string
+ RequestID string
}
-// NewBasicClient constructs a StorageClient with given storage service name
-// and key.
-func NewBasicClient(accountName, accountKey string) (StorageClient, error) {
- return NewClient(accountName, accountKey, DefaultBaseUrl, DefaultApiVersion, defaultUseHttps)
+// 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
}
-// NewClient constructs a StorageClient. 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, blobServiceBaseUrl, apiVersion string, useHttps bool) (StorageClient, error) {
- var c StorageClient
+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", got, strings.Join(expected, " or "))
+}
+
+// NewBasicClient constructs a Client with given storage service name and
+// key.
+func NewBasicClient(accountName, accountKey string) (Client, error) {
+ return NewClient(accountName, accountKey, DefaultBaseURL, DefaultAPIVersion, defaultUseHTTPS)
+}
+
+// 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, blobServiceBaseURL, apiVersion string, useHTTPS bool) (Client, error) {
+ var c Client
if accountName == "" {
return c, fmt.Errorf("azure: account name required")
} else if accountKey == "" {
return c, fmt.Errorf("azure: account key required")
- } else if blobServiceBaseUrl == "" {
+ } else if blobServiceBaseURL == "" {
return c, fmt.Errorf("azure: base storage service url required")
}
@@ -78,22 +103,22 @@ func NewClient(accountName, accountKey, blobServiceBaseUrl, apiVersion string, u
return c, err
}
- return StorageClient{
+ return Client{
accountName: accountName,
accountKey: key,
- useHttps: useHttps,
- baseUrl: blobServiceBaseUrl,
+ useHTTPS: useHTTPS,
+ baseURL: blobServiceBaseURL,
apiVersion: apiVersion,
}, nil
}
-func (c StorageClient) getBaseUrl(service string) string {
+func (c Client) getBaseURL(service string) string {
scheme := "http"
- if c.useHttps {
+ if c.useHTTPS {
scheme = "https"
}
- host := fmt.Sprintf("%s.%s.%s", c.accountName, service, c.baseUrl)
+ host := fmt.Sprintf("%s.%s.%s", c.accountName, service, c.baseURL)
u := &url.URL{
Scheme: scheme,
@@ -101,8 +126,8 @@ func (c StorageClient) getBaseUrl(service string) string {
return u.String()
}
-func (c StorageClient) getEndpoint(service, path string, params url.Values) string {
- u, err := url.Parse(c.getBaseUrl(service))
+func (c Client) getEndpoint(service, path string, params url.Values) string {
+ u, err := url.Parse(c.getBaseURL(service))
if err != nil {
// really should not be happening
panic(err)
@@ -117,18 +142,24 @@ func (c StorageClient) getEndpoint(service, path string, params url.Values) stri
return u.String()
}
-// GetBlobService returns a BlobStorageClient which can operate on the
-// blob service of the storage account.
-func (c StorageClient) GetBlobService() *BlobStorageClient {
- return &BlobStorageClient{c}
+// GetBlobService returns a BlobStorageClient which can operate on the blob
+// service of the storage account.
+func (c Client) GetBlobService() BlobStorageClient {
+ return BlobStorageClient{c}
}
-func (c StorageClient) createAuthorizationHeader(canonicalizedString string) string {
+// GetQueueService returns a QueueServiceClient which can operate on the queue
+// service of the storage account.
+func (c Client) GetQueueService() QueueServiceClient {
+ return QueueServiceClient{c}
+}
+
+func (c Client) createAuthorizationHeader(canonicalizedString string) string {
signature := c.computeHmac256(canonicalizedString)
return fmt.Sprintf("%s %s:%s", "SharedKey", c.accountName, signature)
}
-func (c StorageClient) getAuthorizationHeader(verb, url string, headers map[string]string) (string, error) {
+func (c Client) getAuthorizationHeader(verb, url string, headers map[string]string) (string, error) {
canonicalizedResource, err := c.buildCanonicalizedResource(url)
if err != nil {
return "", err
@@ -138,14 +169,14 @@ func (c StorageClient) getAuthorizationHeader(verb, url string, headers map[stri
return c.createAuthorizationHeader(canonicalizedString), nil
}
-func (c StorageClient) getStandardHeaders() map[string]string {
+func (c Client) getStandardHeaders() map[string]string {
return map[string]string{
"x-ms-version": c.apiVersion,
"x-ms-date": currentTimeRfc1123Formatted(),
}
}
-func (c StorageClient) buildCanonicalizedHeader(headers map[string]string) string {
+func (c Client) buildCanonicalizedHeader(headers map[string]string) string {
cm := make(map[string]string)
for k, v := range headers {
@@ -179,7 +210,7 @@ func (c StorageClient) buildCanonicalizedHeader(headers map[string]string) strin
return ch
}
-func (c StorageClient) buildCanonicalizedResource(uri string) (string, error) {
+func (c Client) buildCanonicalizedResource(uri string) (string, error) {
errMsg := "buildCanonicalizedResource error: %s"
u, err := url.Parse(uri)
if err != nil {
@@ -220,7 +251,7 @@ func (c StorageClient) buildCanonicalizedResource(uri string) (string, error) {
return cr, nil
}
-func (c StorageClient) buildCanonicalizedString(verb string, headers map[string]string, canonicalizedResource string) string {
+func (c Client) buildCanonicalizedString(verb string, headers map[string]string, canonicalizedResource string) string {
canonicalizedString := 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",
verb,
headers["Content-Encoding"],
@@ -240,7 +271,7 @@ func (c StorageClient) buildCanonicalizedString(verb string, headers map[string]
return canonicalizedString
}
-func (c StorageClient) exec(verb, url string, headers map[string]string, body io.Reader) (*storageResponse, error) {
+func (c Client) exec(verb, url string, headers map[string]string, body io.Reader) (*storageResponse, error) {
authHeader, err := c.getAuthorizationHeader(verb, url, headers)
if err != nil {
return nil, err
@@ -271,10 +302,10 @@ func (c StorageClient) exec(verb, url string, headers map[string]string, body io
if len(respBody) == 0 {
// no error in response body
- err = fmt.Errorf("storage: service returned without a response body (%s).", resp.Status)
+ err = fmt.Errorf("storage: service returned without a response body (%s)", resp.Status)
} else {
// response contains storage service error object, unmarshal
- storageErr, errIn := serviceErrFromXml(respBody, resp.StatusCode, resp.Header.Get("x-ms-request-id"))
+ storageErr, errIn := serviceErrFromXML(respBody, resp.StatusCode, resp.Header.Get("x-ms-request-id"))
if err != nil { // error unmarshaling the error response
err = errIn
}
@@ -302,16 +333,27 @@ func readResponseBody(resp *http.Response) ([]byte, error) {
return out, err
}
-func serviceErrFromXml(body []byte, statusCode int, requestId string) (StorageServiceError, error) {
- var storageErr StorageServiceError
+func serviceErrFromXML(body []byte, statusCode int, requestID string) (AzureStorageServiceError, error) {
+ var storageErr AzureStorageServiceError
if err := xml.Unmarshal(body, &storageErr); err != nil {
return storageErr, err
}
storageErr.StatusCode = statusCode
- storageErr.RequestId = requestId
+ storageErr.RequestID = requestID
return storageErr, nil
}
-func (e StorageServiceError) Error() string {
- return fmt.Sprintf("storage: remote server returned error. StatusCode=%d, ErrorCode=%s, ErrorMessage=%s, RequestId=%s", e.StatusCode, e.Code, e.Message, e.RequestId)
+func (e AzureStorageServiceError) Error() string {
+ return fmt.Sprintf("storage: service returned error: StatusCode=%d, ErrorCode=%s, ErrorMessage=%s, RequestId=%s", e.StatusCode, e.Code, e.Message, e.RequestID)
+}
+
+// checkRespCode returns UnexpectedStatusError if the given response code is not
+// one of the allowed status codes; otherwise nil.
+func checkRespCode(respCode int, allowed []int) error {
+ for _, v := range allowed {
+ if respCode == v {
+ return nil
+ }
+ }
+ return UnexpectedStatusCodeError{allowed, respCode}
}
diff --git a/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/client_test.go b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/client_test.go
new file mode 100644
index 000000000..5bc521100
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/client_test.go
@@ -0,0 +1,156 @@
+package storage
+
+import (
+ "encoding/base64"
+ "net/url"
+ "os"
+ "testing"
+
+ chk "gopkg.in/check.v1"
+)
+
+// Hook up gocheck to testing
+func Test(t *testing.T) { chk.TestingT(t) }
+
+type StorageClientSuite struct{}
+
+var _ = chk.Suite(&StorageClientSuite{})
+
+// getBasicClient returns a test client from storage credentials in the env
+func getBasicClient(c *chk.C) Client {
+ name := os.Getenv("ACCOUNT_NAME")
+ if name == "" {
+ c.Fatal("ACCOUNT_NAME not set, need an empty storage account to test")
+ }
+ key := os.Getenv("ACCOUNT_KEY")
+ if key == "" {
+ c.Fatal("ACCOUNT_KEY not set")
+ }
+ cli, err := NewBasicClient(name, key)
+ c.Assert(err, chk.IsNil)
+ return cli
+}
+
+func (s *StorageClientSuite) TestGetBaseURL_Basic_Https(c *chk.C) {
+ cli, err := NewBasicClient("foo", "YmFy")
+ c.Assert(err, chk.IsNil)
+ c.Assert(cli.apiVersion, chk.Equals, DefaultAPIVersion)
+ c.Assert(err, chk.IsNil)
+ c.Assert(cli.getBaseURL("table"), chk.Equals, "https://foo.table.core.windows.net")
+}
+
+func (s *StorageClientSuite) TestGetBaseURL_Custom_NoHttps(c *chk.C) {
+ apiVersion := "2015-01-01" // a non existing one
+ cli, err := NewClient("foo", "YmFy", "core.chinacloudapi.cn", apiVersion, false)
+ c.Assert(err, chk.IsNil)
+ c.Assert(cli.apiVersion, chk.Equals, apiVersion)
+ c.Assert(cli.getBaseURL("table"), chk.Equals, "http://foo.table.core.chinacloudapi.cn")
+}
+
+func (s *StorageClientSuite) TestGetEndpoint_None(c *chk.C) {
+ cli, err := NewBasicClient("foo", "YmFy")
+ c.Assert(err, chk.IsNil)
+ output := cli.getEndpoint(blobServiceName, "", url.Values{})
+ c.Assert(output, chk.Equals, "https://foo.blob.core.windows.net/")
+}
+
+func (s *StorageClientSuite) TestGetEndpoint_PathOnly(c *chk.C) {
+ cli, err := NewBasicClient("foo", "YmFy")
+ c.Assert(err, chk.IsNil)
+ output := cli.getEndpoint(blobServiceName, "path", url.Values{})
+ c.Assert(output, chk.Equals, "https://foo.blob.core.windows.net/path")
+}
+
+func (s *StorageClientSuite) TestGetEndpoint_ParamsOnly(c *chk.C) {
+ cli, err := NewBasicClient("foo", "YmFy")
+ c.Assert(err, chk.IsNil)
+ params := url.Values{}
+ params.Set("a", "b")
+ params.Set("c", "d")
+ output := cli.getEndpoint(blobServiceName, "", params)
+ c.Assert(output, chk.Equals, "https://foo.blob.core.windows.net/?a=b&c=d")
+}
+
+func (s *StorageClientSuite) TestGetEndpoint_Mixed(c *chk.C) {
+ cli, err := NewBasicClient("foo", "YmFy")
+ c.Assert(err, chk.IsNil)
+ params := url.Values{}
+ params.Set("a", "b")
+ params.Set("c", "d")
+ output := cli.getEndpoint(blobServiceName, "path", params)
+ c.Assert(output, chk.Equals, "https://foo.blob.core.windows.net/path?a=b&c=d")
+}
+
+func (s *StorageClientSuite) Test_getStandardHeaders(c *chk.C) {
+ cli, err := NewBasicClient("foo", "YmFy")
+ c.Assert(err, chk.IsNil)
+
+ headers := cli.getStandardHeaders()
+ c.Assert(len(headers), chk.Equals, 2)
+ c.Assert(headers["x-ms-version"], chk.Equals, cli.apiVersion)
+ if _, ok := headers["x-ms-date"]; !ok {
+ c.Fatal("Missing date header")
+ }
+}
+
+func (s *StorageClientSuite) Test_buildCanonicalizedResource(c *chk.C) {
+ cli, err := NewBasicClient("foo", "YmFy")
+ c.Assert(err, chk.IsNil)
+
+ type test struct{ url, expected string }
+ tests := []test{
+ {"https://foo.blob.core.windows.net/path?a=b&c=d", "/foo/path\na:b\nc:d"},
+ {"https://foo.blob.core.windows.net/?comp=list", "/foo/\ncomp:list"},
+ {"https://foo.blob.core.windows.net/cnt/blob", "/foo/cnt/blob"},
+ }
+
+ for _, i := range tests {
+ out, err := cli.buildCanonicalizedResource(i.url)
+ c.Assert(err, chk.IsNil)
+ c.Assert(out, chk.Equals, i.expected)
+ }
+}
+
+func (s *StorageClientSuite) Test_buildCanonicalizedHeader(c *chk.C) {
+ cli, err := NewBasicClient("foo", "YmFy")
+ c.Assert(err, chk.IsNil)
+
+ type test struct {
+ headers map[string]string
+ expected string
+ }
+ tests := []test{
+ {map[string]string{}, ""},
+ {map[string]string{"x-ms-foo": "bar"}, "x-ms-foo:bar"},
+ {map[string]string{"foo:": "bar"}, ""},
+ {map[string]string{"foo:": "bar", "x-ms-foo": "bar"}, "x-ms-foo:bar"},
+ {map[string]string{
+ "x-ms-version": "9999-99-99",
+ "x-ms-blob-type": "BlockBlob"}, "x-ms-blob-type:BlockBlob\nx-ms-version:9999-99-99"}}
+
+ for _, i := range tests {
+ c.Assert(cli.buildCanonicalizedHeader(i.headers), chk.Equals, i.expected)
+ }
+}
+
+func (s *StorageClientSuite) TestReturnsStorageServiceError(c *chk.C) {
+ // attempt to delete a nonexisting container
+ _, err := getBlobClient(c).deleteContainer(randContainer())
+ c.Assert(err, chk.NotNil)
+
+ v, ok := err.(AzureStorageServiceError)
+ c.Check(ok, chk.Equals, true)
+ c.Assert(v.StatusCode, chk.Equals, 404)
+ c.Assert(v.Code, chk.Equals, "ContainerNotFound")
+ c.Assert(v.Code, chk.Not(chk.Equals), "")
+}
+
+func (s *StorageClientSuite) Test_createAuthorizationHeader(c *chk.C) {
+ key := base64.StdEncoding.EncodeToString([]byte("bar"))
+ cli, err := NewBasicClient("foo", key)
+ c.Assert(err, chk.IsNil)
+
+ canonicalizedString := `foobarzoo`
+ expected := `SharedKey foo:h5U0ATVX6SpbFX1H6GNuxIMeXXCILLoIvhflPtuQZ30=`
+ c.Assert(cli.createAuthorizationHeader(canonicalizedString), chk.Equals, expected)
+}
diff --git a/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/queue.go b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/queue.go
new file mode 100644
index 000000000..fa017f4c8
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/queue.go
@@ -0,0 +1,230 @@
+package storage
+
+import (
+ "encoding/xml"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strconv"
+)
+
+// QueueServiceClient contains operations for Microsoft Azure Queue Storage
+// Service.
+type QueueServiceClient struct {
+ client Client
+}
+
+func pathForQueue(queue string) string { return fmt.Sprintf("/%s", queue) }
+func pathForQueueMessages(queue string) string { return fmt.Sprintf("/%s/messages", queue) }
+func pathForMessage(queue, name string) string { return fmt.Sprintf("/%s/messages/%s", queue, name) }
+
+type putMessageRequest struct {
+ XMLName xml.Name `xml:"QueueMessage"`
+ MessageText string `xml:"MessageText"`
+}
+
+// PutMessageParameters is the set of options can be specified for Put Messsage
+// operation. A zero struct does not use any preferences for the request.
+type PutMessageParameters struct {
+ VisibilityTimeout int
+ MessageTTL int
+}
+
+func (p PutMessageParameters) getParameters() url.Values {
+ out := url.Values{}
+ if p.VisibilityTimeout != 0 {
+ out.Set("visibilitytimeout", strconv.Itoa(p.VisibilityTimeout))
+ }
+ if p.MessageTTL != 0 {
+ out.Set("messagettl", strconv.Itoa(p.MessageTTL))
+ }
+ return out
+}
+
+// GetMessagesParameters is the set of options can be specified for Get
+// Messsages operation. A zero struct does not use any preferences for the
+// request.
+type GetMessagesParameters struct {
+ NumOfMessages int
+ VisibilityTimeout int
+}
+
+func (p GetMessagesParameters) getParameters() url.Values {
+ out := url.Values{}
+ if p.NumOfMessages != 0 {
+ out.Set("numofmessages", strconv.Itoa(p.NumOfMessages))
+ }
+ if p.VisibilityTimeout != 0 {
+ out.Set("visibilitytimeout", strconv.Itoa(p.VisibilityTimeout))
+ }
+ return out
+}
+
+// PeekMessagesParameters is the set of options can be specified for Peek
+// Messsage operation. A zero struct does not use any preferences for the
+// request.
+type PeekMessagesParameters struct {
+ NumOfMessages int
+}
+
+func (p PeekMessagesParameters) getParameters() url.Values {
+ out := url.Values{"peekonly": {"true"}} // Required for peek operation
+ if p.NumOfMessages != 0 {
+ out.Set("numofmessages", strconv.Itoa(p.NumOfMessages))
+ }
+ return out
+}
+
+// GetMessagesResponse represents a response returned from Get Messages
+// operation.
+type GetMessagesResponse struct {
+ XMLName xml.Name `xml:"QueueMessagesList"`
+ QueueMessagesList []GetMessageResponse `xml:"QueueMessage"`
+}
+
+// GetMessageResponse represents a QueueMessage object returned from Get
+// Messages operation response.
+type GetMessageResponse struct {
+ MessageID string `xml:"MessageId"`
+ InsertionTime string `xml:"InsertionTime"`
+ ExpirationTime string `xml:"ExpirationTime"`
+ PopReceipt string `xml:"PopReceipt"`
+ TimeNextVisible string `xml:"TimeNextVisible"`
+ DequeueCount int `xml:"DequeueCount"`
+ MessageText string `xml:"MessageText"`
+}
+
+// PeekMessagesResponse represents a response returned from Get Messages
+// operation.
+type PeekMessagesResponse struct {
+ XMLName xml.Name `xml:"QueueMessagesList"`
+ QueueMessagesList []PeekMessageResponse `xml:"QueueMessage"`
+}
+
+// PeekMessageResponse represents a QueueMessage object returned from Peek
+// Messages operation response.
+type PeekMessageResponse struct {
+ MessageID string `xml:"MessageId"`
+ InsertionTime string `xml:"InsertionTime"`
+ ExpirationTime string `xml:"ExpirationTime"`
+ DequeueCount int `xml:"DequeueCount"`
+ MessageText string `xml:"MessageText"`
+}
+
+// CreateQueue operation creates a queue under the given account.
+//
+// See https://msdn.microsoft.com/en-us/library/azure/dd179342.aspx
+func (c QueueServiceClient) CreateQueue(name string) error {
+ uri := c.client.getEndpoint(queueServiceName, pathForQueue(name), url.Values{})
+ headers := c.client.getStandardHeaders()
+ headers["Content-Length"] = "0"
+ resp, err := c.client.exec("PUT", uri, headers, nil)
+ if err != nil {
+ return err
+ }
+ defer resp.body.Close()
+ return checkRespCode(resp.statusCode, []int{http.StatusCreated})
+}
+
+// DeleteQueue operation permanently deletes the specified queue.
+//
+// See https://msdn.microsoft.com/en-us/library/azure/dd179436.aspx
+func (c QueueServiceClient) DeleteQueue(name string) error {
+ uri := c.client.getEndpoint(queueServiceName, pathForQueue(name), url.Values{})
+ resp, err := c.client.exec("DELETE", uri, c.client.getStandardHeaders(), nil)
+ if err != nil {
+ return err
+ }
+ defer resp.body.Close()
+ return checkRespCode(resp.statusCode, []int{http.StatusNoContent})
+}
+
+// QueueExists returns true if a queue with given name exists.
+func (c QueueServiceClient) QueueExists(name string) (bool, error) {
+ uri := c.client.getEndpoint(queueServiceName, pathForQueue(name), url.Values{"comp": {"metadata"}})
+ resp, err := c.client.exec("GET", uri, c.client.getStandardHeaders(), nil)
+ if resp != nil && (resp.statusCode == http.StatusOK || resp.statusCode == http.StatusNotFound) {
+ return resp.statusCode == http.StatusOK, nil
+ }
+
+ return false, err
+}
+
+// PutMessage operation adds a new message to the back of the message queue.
+//
+// See https://msdn.microsoft.com/en-us/library/azure/dd179346.aspx
+func (c QueueServiceClient) PutMessage(queue string, message string, params PutMessageParameters) error {
+ uri := c.client.getEndpoint(queueServiceName, pathForQueueMessages(queue), params.getParameters())
+ req := putMessageRequest{MessageText: message}
+ body, nn, err := xmlMarshal(req)
+ if err != nil {
+ return err
+ }
+ headers := c.client.getStandardHeaders()
+ headers["Content-Length"] = strconv.Itoa(nn)
+ resp, err := c.client.exec("POST", uri, headers, body)
+ if err != nil {
+ return err
+ }
+ defer resp.body.Close()
+ return checkRespCode(resp.statusCode, []int{http.StatusCreated})
+}
+
+// ClearMessages operation deletes all messages from the specified queue.
+//
+// See https://msdn.microsoft.com/en-us/library/azure/dd179454.aspx
+func (c QueueServiceClient) ClearMessages(queue string) error {
+ uri := c.client.getEndpoint(queueServiceName, pathForQueueMessages(queue), url.Values{})
+ resp, err := c.client.exec("DELETE", uri, c.client.getStandardHeaders(), nil)
+ if err != nil {
+ return err
+ }
+ defer resp.body.Close()
+ return checkRespCode(resp.statusCode, []int{http.StatusNoContent})
+}
+
+// GetMessages operation retrieves one or more messages from the front of the
+// queue.
+//
+// See https://msdn.microsoft.com/en-us/library/azure/dd179474.aspx
+func (c QueueServiceClient) GetMessages(queue string, params GetMessagesParameters) (GetMessagesResponse, error) {
+ var r GetMessagesResponse
+ uri := c.client.getEndpoint(queueServiceName, pathForQueueMessages(queue), params.getParameters())
+ resp, err := c.client.exec("GET", uri, c.client.getStandardHeaders(), nil)
+ if err != nil {
+ return r, err
+ }
+ defer resp.body.Close()
+ err = xmlUnmarshal(resp.body, &r)
+ return r, err
+}
+
+// PeekMessages retrieves one or more messages from the front of the queue, but
+// does not alter the visibility of the message.
+//
+// See https://msdn.microsoft.com/en-us/library/azure/dd179472.aspx
+func (c QueueServiceClient) PeekMessages(queue string, params PeekMessagesParameters) (PeekMessagesResponse, error) {
+ var r PeekMessagesResponse
+ uri := c.client.getEndpoint(queueServiceName, pathForQueueMessages(queue), params.getParameters())
+ resp, err := c.client.exec("GET", uri, c.client.getStandardHeaders(), nil)
+ if err != nil {
+ return r, err
+ }
+ defer resp.body.Close()
+ err = xmlUnmarshal(resp.body, &r)
+ return r, err
+}
+
+// DeleteMessage operation deletes the specified message.
+//
+// See https://msdn.microsoft.com/en-us/library/azure/dd179347.aspx
+func (c QueueServiceClient) DeleteMessage(queue, messageID, popReceipt string) error {
+ uri := c.client.getEndpoint(queueServiceName, pathForMessage(queue, messageID), url.Values{
+ "popreceipt": {popReceipt}})
+ resp, err := c.client.exec("DELETE", uri, c.client.getStandardHeaders(), nil)
+ if err != nil {
+ return err
+ }
+ defer resp.body.Close()
+ return checkRespCode(resp.statusCode, []int{http.StatusNoContent})
+}
diff --git a/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/queue_test.go b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/queue_test.go
new file mode 100644
index 000000000..5c7bad93c
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/queue_test.go
@@ -0,0 +1,91 @@
+package storage
+
+import (
+ chk "gopkg.in/check.v1"
+)
+
+type StorageQueueSuite struct{}
+
+var _ = chk.Suite(&StorageQueueSuite{})
+
+func getQueueClient(c *chk.C) QueueServiceClient {
+ return getBasicClient(c).GetQueueService()
+}
+
+func (s *StorageQueueSuite) Test_pathForQueue(c *chk.C) {
+ c.Assert(pathForQueue("q"), chk.Equals, "/q")
+}
+
+func (s *StorageQueueSuite) Test_pathForQueueMessages(c *chk.C) {
+ c.Assert(pathForQueueMessages("q"), chk.Equals, "/q/messages")
+}
+
+func (s *StorageQueueSuite) Test_pathForMessage(c *chk.C) {
+ c.Assert(pathForMessage("q", "m"), chk.Equals, "/q/messages/m")
+}
+
+func (s *StorageQueueSuite) TestCreateQueue_DeleteQueue(c *chk.C) {
+ cli := getQueueClient(c)
+ name := randString(20)
+ c.Assert(cli.CreateQueue(name), chk.IsNil)
+ c.Assert(cli.DeleteQueue(name), chk.IsNil)
+}
+
+func (s *StorageQueueSuite) TestQueueExists(c *chk.C) {
+ cli := getQueueClient(c)
+ ok, err := cli.QueueExists("nonexistent-queue")
+ c.Assert(err, chk.IsNil)
+ c.Assert(ok, chk.Equals, false)
+
+ name := randString(20)
+ c.Assert(cli.CreateQueue(name), chk.IsNil)
+ defer cli.DeleteQueue(name)
+
+ ok, err = cli.QueueExists(name)
+ c.Assert(err, chk.IsNil)
+ c.Assert(ok, chk.Equals, true)
+}
+
+func (s *StorageQueueSuite) TestPostMessage_PeekMessage_DeleteMessage(c *chk.C) {
+ q := randString(20)
+ cli := getQueueClient(c)
+ c.Assert(cli.CreateQueue(q), chk.IsNil)
+ defer cli.DeleteQueue(q)
+
+ msg := randString(64 * 1024) // exercise max length
+ c.Assert(cli.PutMessage(q, msg, PutMessageParameters{}), chk.IsNil)
+ r, err := cli.PeekMessages(q, PeekMessagesParameters{})
+ c.Assert(err, chk.IsNil)
+ c.Assert(len(r.QueueMessagesList), chk.Equals, 1)
+ c.Assert(r.QueueMessagesList[0].MessageText, chk.Equals, msg)
+}
+
+func (s *StorageQueueSuite) TestGetMessages(c *chk.C) {
+ q := randString(20)
+ cli := getQueueClient(c)
+ c.Assert(cli.CreateQueue(q), chk.IsNil)
+ defer cli.DeleteQueue(q)
+
+ n := 4
+ for i := 0; i < n; i++ {
+ c.Assert(cli.PutMessage(q, randString(10), PutMessageParameters{}), chk.IsNil)
+ }
+
+ r, err := cli.GetMessages(q, GetMessagesParameters{NumOfMessages: n})
+ c.Assert(err, chk.IsNil)
+ c.Assert(len(r.QueueMessagesList), chk.Equals, n)
+}
+
+func (s *StorageQueueSuite) TestDeleteMessages(c *chk.C) {
+ q := randString(20)
+ cli := getQueueClient(c)
+ c.Assert(cli.CreateQueue(q), chk.IsNil)
+ defer cli.DeleteQueue(q)
+
+ c.Assert(cli.PutMessage(q, "message", PutMessageParameters{}), chk.IsNil)
+ r, err := cli.GetMessages(q, GetMessagesParameters{VisibilityTimeout: 1})
+ c.Assert(err, chk.IsNil)
+ c.Assert(len(r.QueueMessagesList), chk.Equals, 1)
+ m := r.QueueMessagesList[0]
+ c.Assert(cli.DeleteMessage(q, m.MessageID, m.PopReceipt), chk.IsNil)
+}
diff --git a/Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/storage/util.go b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/util.go
similarity index 73%
rename from Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/storage/util.go
rename to Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/util.go
index 8a0f7b945..33155af7f 100644
--- a/Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/storage/util.go
+++ b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/util.go
@@ -1,6 +1,7 @@
package storage
import (
+ "bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
@@ -13,7 +14,7 @@ import (
"time"
)
-func (c StorageClient) computeHmac256(message string) string {
+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))
@@ -47,17 +48,24 @@ func mergeParams(v1, v2 url.Values) url.Values {
func prepareBlockListRequest(blocks []Block) string {
s := ``
for _, v := range blocks {
- s += fmt.Sprintf("<%s>%s%s>", v.Status, v.Id, v.Status)
+ s += fmt.Sprintf("<%s>%s%s>", v.Status, v.ID, v.Status)
}
s += ``
return s
}
-func xmlUnmarshal(body io.ReadCloser, v interface{}) error {
+func xmlUnmarshal(body io.Reader, v interface{}) error {
data, err := ioutil.ReadAll(body)
if err != nil {
return err
}
- defer body.Close()
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
+}
diff --git a/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/util_test.go b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/util_test.go
new file mode 100644
index 000000000..9bf82dcc3
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/Azure/azure-sdk-for-go/storage/util_test.go
@@ -0,0 +1,69 @@
+package storage
+
+import (
+ "encoding/xml"
+ "io/ioutil"
+ "net/url"
+ "strings"
+ "time"
+
+ chk "gopkg.in/check.v1"
+)
+
+func (s *StorageClientSuite) Test_timeRfc1123Formatted(c *chk.C) {
+ now := time.Now().UTC()
+ expectedLayout := "Mon, 02 Jan 2006 15:04:05 GMT"
+ c.Assert(timeRfc1123Formatted(now), chk.Equals, now.Format(expectedLayout))
+}
+
+func (s *StorageClientSuite) Test_mergeParams(c *chk.C) {
+ v1 := url.Values{
+ "k1": {"v1"},
+ "k2": {"v2"}}
+ v2 := url.Values{
+ "k1": {"v11"},
+ "k3": {"v3"}}
+ out := mergeParams(v1, v2)
+ c.Assert(out.Get("k1"), chk.Equals, "v1")
+ c.Assert(out.Get("k2"), chk.Equals, "v2")
+ c.Assert(out.Get("k3"), chk.Equals, "v3")
+ c.Assert(out["k1"], chk.DeepEquals, []string{"v1", "v11"})
+}
+
+func (s *StorageClientSuite) Test_prepareBlockListRequest(c *chk.C) {
+ empty := []Block{}
+ expected := ``
+ c.Assert(prepareBlockListRequest(empty), chk.DeepEquals, expected)
+
+ blocks := []Block{{"foo", BlockStatusLatest}, {"bar", BlockStatusUncommitted}}
+ expected = `foobar`
+ c.Assert(prepareBlockListRequest(blocks), chk.DeepEquals, expected)
+}
+
+func (s *StorageClientSuite) Test_xmlUnmarshal(c *chk.C) {
+ xml := `
+
+ myblob
+ `
+ var blob Blob
+ body := ioutil.NopCloser(strings.NewReader(xml))
+ c.Assert(xmlUnmarshal(body, &blob), chk.IsNil)
+ c.Assert(blob.Name, chk.Equals, "myblob")
+}
+
+func (s *StorageClientSuite) Test_xmlMarshal(c *chk.C) {
+ type t struct {
+ XMLName xml.Name `xml:"S"`
+ Name string `xml:"Name"`
+ }
+
+ b := t{Name: "myblob"}
+ expected := `myblob`
+ r, i, err := xmlMarshal(b)
+ c.Assert(err, chk.IsNil)
+ o, err := ioutil.ReadAll(r)
+ c.Assert(err, chk.IsNil)
+ out := string(o)
+ c.Assert(out, chk.Equals, expected)
+ c.Assert(i, chk.Equals, len(expected))
+}
diff --git a/Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/storage/blob_test.go b/Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/storage/blob_test.go
deleted file mode 100644
index 33e3d173d..000000000
--- a/Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/storage/blob_test.go
+++ /dev/null
@@ -1,1123 +0,0 @@
-package storage
-
-import (
- "bytes"
- "crypto/rand"
- "encoding/base64"
- "errors"
- "fmt"
- "io/ioutil"
- "net/http"
- "net/url"
- "os"
- "reflect"
- "sort"
- "strings"
- "sync"
- "testing"
- "time"
-)
-
-const testContainerPrefix = "zzzztest-"
-
-func Test_pathForContainer(t *testing.T) {
- out := pathForContainer("foo")
- if expected := "/foo"; out != expected {
- t.Errorf("Wrong pathForContainer. Expected: '%s', got: '%s'", expected, out)
- }
-}
-
-func Test_pathForBlob(t *testing.T) {
- out := pathForBlob("foo", "blob")
- if expected := "/foo/blob"; out != expected {
- t.Errorf("Wrong pathForBlob. Expected: '%s', got: '%s'", expected, out)
- }
-}
-
-func Test_blobSASStringToSign(t *testing.T) {
- _, err := blobSASStringToSign("2012-02-12", "CS", "SE", "SP")
- if err == nil {
- t.Fatal("Expected error, got nil")
- }
-
- out, err := blobSASStringToSign("2013-08-15", "CS", "SE", "SP")
- if err != nil {
- t.Fatal(err)
- }
- if expected := "SP\n\nSE\nCS\n\n2013-08-15\n\n\n\n\n"; out != expected {
- t.Errorf("Wrong stringToSign. Expected: '%s', got: '%s'", expected, out)
- }
-}
-
-func TestGetBlobSASURI(t *testing.T) {
- api, err := NewClient("foo", "YmFy", DefaultBaseUrl, "2013-08-15", true)
- if err != nil {
- t.Fatal(err)
- }
- cli := api.GetBlobService()
- expiry := time.Time{}
-
- expectedParts := url.URL{
- Scheme: "https",
- Host: "foo.blob.core.windows.net",
- Path: "container/name",
- RawQuery: url.Values{
- "sv": {"2013-08-15"},
- "sig": {"/OXG7rWh08jYwtU03GzJM0DHZtidRGpC6g69rSGm3I0="},
- "sr": {"b"},
- "sp": {"r"},
- "se": {"0001-01-01T00:00:00Z"},
- }.Encode()}
-
- u, err := cli.GetBlobSASURI("container", "name", expiry, "r")
- if err != nil {
- t.Fatal(err)
- }
- sasParts, err := url.Parse(u)
- if err != nil {
- t.Fatal(err)
- }
-
- expectedQuery := expectedParts.Query()
- sasQuery := sasParts.Query()
-
- expectedParts.RawQuery = "" // reset
- sasParts.RawQuery = ""
-
- if expectedParts.String() != sasParts.String() {
- t.Fatalf("Base URL wrong for SAS. Expected: '%s', got: '%s'", expectedParts, sasParts)
- }
-
- if len(expectedQuery) != len(sasQuery) {
- t.Fatalf("Query string wrong for SAS URL. Expected: '%d keys', got: '%d keys'", len(expectedQuery), len(sasQuery))
- }
-
- for k, v := range expectedQuery {
- out, ok := sasQuery[k]
- if !ok {
- t.Fatalf("Query parameter '%s' not found in generated SAS query. Expected: '%s'", k, v)
- }
- if !reflect.DeepEqual(v, out) {
- t.Fatalf("Wrong value for query parameter '%s'. Expected: '%s', got: '%s'", k, v, out)
- }
- }
-}
-
-func TestBlobSASURICorrectness(t *testing.T) {
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
- cnt := randContainer()
- blob := randString(20)
- body := []byte(randString(100))
- expiry := time.Now().UTC().Add(time.Hour)
- permissions := "r"
-
- err = cli.CreateContainer(cnt, ContainerAccessTypePrivate)
- if err != nil {
- t.Fatal(err)
- }
- defer cli.DeleteContainer(cnt)
-
- err = cli.PutBlockBlob(cnt, blob, bytes.NewReader(body))
- if err != nil {
- t.Fatal(err)
- }
-
- sasUri, err := cli.GetBlobSASURI(cnt, blob, expiry, permissions)
- if err != nil {
- t.Fatal(err)
- }
-
- resp, err := http.Get(sasUri)
- if err != nil {
- t.Logf("SAS URI: %s", sasUri)
- t.Fatal(err)
- }
-
- blobResp, err := ioutil.ReadAll(resp.Body)
- defer resp.Body.Close()
- if err != nil {
- t.Fatal(err)
- }
-
- if resp.StatusCode != http.StatusOK {
- t.Fatalf("Non-ok status code: %s", resp.Status)
- }
-
- if len(blobResp) != len(body) {
- t.Fatalf("Wrong blob size on SAS URI. Expected: %d, Got: %d", len(body), len(blobResp))
- }
-}
-
-func TestListContainersPagination(t *testing.T) {
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- err = deleteTestContainers(cli)
- if err != nil {
- t.Fatal(err)
- }
-
- const n = 5
- const pageSize = 2
-
- // Create test containers
- created := []string{}
- for i := 0; i < n; i++ {
- name := randContainer()
- err := cli.CreateContainer(name, ContainerAccessTypePrivate)
- if err != nil {
- t.Fatalf("Error creating test container: %s", err)
- }
- created = append(created, name)
- }
- sort.Strings(created)
-
- // Defer test container deletions
- defer func() {
- var wg sync.WaitGroup
- for _, cnt := range created {
- wg.Add(1)
- go func(name string) {
- err := cli.DeleteContainer(name)
- if err != nil {
- t.Logf("Error while deleting test container: %s", err)
- }
- wg.Done()
- }(cnt)
- }
- wg.Wait()
- }()
-
- // Paginate results
- seen := []string{}
- marker := ""
- for {
- resp, err := cli.ListContainers(ListContainersParameters{
- Prefix: testContainerPrefix,
- MaxResults: pageSize,
- Marker: marker})
-
- if err != nil {
- t.Fatal(err)
- }
-
- containers := resp.Containers
-
- if len(containers) > pageSize {
- t.Fatalf("Got a bigger page. Expected: %d, got: %d", pageSize, len(containers))
- }
-
- for _, c := range containers {
- seen = append(seen, c.Name)
- }
-
- marker = resp.NextMarker
- if marker == "" || len(containers) == 0 {
- break
- }
- }
-
- // Compare
- if !reflect.DeepEqual(created, seen) {
- t.Fatalf("Wrong pagination results:\nExpected:\t\t%v\nGot:\t\t%v", created, seen)
- }
-}
-
-func TestContainerExists(t *testing.T) {
- cnt := randContainer()
-
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- ok, err := cli.ContainerExists(cnt)
- if err != nil {
- t.Fatal(err)
- }
- if ok {
- t.Fatalf("Non-existing container returned as existing: %s", cnt)
- }
-
- err = cli.CreateContainer(cnt, ContainerAccessTypeBlob)
- if err != nil {
- t.Fatal(err)
- }
- defer cli.DeleteContainer(cnt)
-
- ok, err = cli.ContainerExists(cnt)
- if err != nil {
- t.Fatal(err)
- }
- if !ok {
- t.Fatalf("Existing container returned as non-existing: %s", cnt)
- }
-}
-
-func TestCreateDeleteContainer(t *testing.T) {
- cnt := randContainer()
-
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- err = cli.CreateContainer(cnt, ContainerAccessTypePrivate)
- if err != nil {
- t.Fatal(err)
- }
- defer cli.DeleteContainer(cnt)
-
- err = cli.DeleteContainer(cnt)
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func TestCreateContainerIfNotExists(t *testing.T) {
- cnt := randContainer()
-
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- // First create
- ok, err := cli.CreateContainerIfNotExists(cnt, ContainerAccessTypePrivate)
- if err != nil {
- t.Fatal(err)
- }
- if expected := true; ok != expected {
- t.Fatalf("Wrong creation status. Expected: %v; Got: %v", expected, ok)
- }
-
- // Second create, should not give errors
- ok, err = cli.CreateContainerIfNotExists(cnt, ContainerAccessTypePrivate)
- if err != nil {
- t.Fatal(err)
- }
- if expected := false; ok != expected {
- t.Fatalf("Wrong creation status. Expected: %v; Got: %v", expected, ok)
- }
-
- defer cli.DeleteContainer(cnt)
-}
-
-func TestDeleteContainerIfExists(t *testing.T) {
- cnt := randContainer()
-
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- // Nonexisting container
- err = cli.DeleteContainer(cnt)
- if err == nil {
- t.Fatal("Expected error, got nil")
- }
-
- ok, err := cli.DeleteContainerIfExists(cnt)
- if err != nil {
- t.Fatalf("Not supposed to return error, got: %s", err)
- }
- if expected := false; ok != expected {
- t.Fatalf("Wrong deletion status. Expected: %v; Got: %v", expected, ok)
- }
-
- // Existing container
- err = cli.CreateContainer(cnt, ContainerAccessTypePrivate)
- if err != nil {
- t.Fatal(err)
- }
- ok, err = cli.DeleteContainerIfExists(cnt)
- if err != nil {
- t.Fatalf("Not supposed to return error, got: %s", err)
- }
- if expected := true; ok != expected {
- t.Fatalf("Wrong deletion status. Expected: %v; Got: %v", expected, ok)
- }
-}
-
-func TestBlobExists(t *testing.T) {
- cnt := randContainer()
- blob := randString(20)
-
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- err = cli.CreateContainer(cnt, ContainerAccessTypeBlob)
- if err != nil {
- t.Fatal(err)
- }
- defer cli.DeleteContainer(cnt)
- err = cli.PutBlockBlob(cnt, blob, strings.NewReader("Hello!"))
- if err != nil {
- t.Fatal(err)
- }
- defer cli.DeleteBlob(cnt, blob)
-
- ok, err := cli.BlobExists(cnt, blob+".foo")
- if err != nil {
- t.Fatal(err)
- }
- if ok {
- t.Errorf("Non-existing blob returned as existing: %s/%s", cnt, blob)
- }
-
- ok, err = cli.BlobExists(cnt, blob)
- if err != nil {
- t.Fatal(err)
- }
- if !ok {
- t.Errorf("Existing blob returned as non-existing: %s/%s", cnt, blob)
- }
-}
-
-func TestGetBlobUrl(t *testing.T) {
- api, err := NewBasicClient("foo", "YmFy")
- if err != nil {
- t.Fatal(err)
- }
- cli := api.GetBlobService()
-
- out := cli.GetBlobUrl("c", "nested/blob")
- if expected := "https://foo.blob.core.windows.net/c/nested/blob"; out != expected {
- t.Fatalf("Wrong blob URL. Expected: '%s', got:'%s'", expected, out)
- }
-
- out = cli.GetBlobUrl("", "blob")
- if expected := "https://foo.blob.core.windows.net/$root/blob"; out != expected {
- t.Fatalf("Wrong blob URL. Expected: '%s', got:'%s'", expected, out)
- }
-
- out = cli.GetBlobUrl("", "nested/blob")
- if expected := "https://foo.blob.core.windows.net/$root/nested/blob"; out != expected {
- t.Fatalf("Wrong blob URL. Expected: '%s', got:'%s'", expected, out)
- }
-}
-
-func TestBlobCopy(t *testing.T) {
- if testing.Short() {
- t.Skip("skipping blob copy in short mode, no SLA on async operation")
- }
-
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- cnt := randContainer()
- src := randString(20)
- dst := randString(20)
- body := []byte(randString(1024))
-
- err = cli.CreateContainer(cnt, ContainerAccessTypePrivate)
- if err != nil {
- t.Fatal(err)
- }
- defer cli.deleteContainer(cnt)
-
- err = cli.PutBlockBlob(cnt, src, bytes.NewReader(body))
- if err != nil {
- t.Fatal(err)
- }
- defer cli.DeleteBlob(cnt, src)
-
- err = cli.CopyBlob(cnt, dst, cli.GetBlobUrl(cnt, src))
- if err != nil {
- t.Fatal(err)
- }
- defer cli.DeleteBlob(cnt, dst)
-
- blobBody, err := cli.GetBlob(cnt, dst)
- if err != nil {
- t.Fatal(err)
- }
-
- b, err := ioutil.ReadAll(blobBody)
- defer blobBody.Close()
- if err != nil {
- t.Fatal(err)
- }
-
- if !reflect.DeepEqual(body, b) {
- t.Fatalf("Copied blob is wrong. Expected: %d bytes, got: %d bytes\n%s\n%s", len(body), len(b), body, b)
- }
-}
-
-func TestDeleteBlobIfExists(t *testing.T) {
- cnt := randContainer()
- blob := randString(20)
-
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- err = cli.DeleteBlob(cnt, blob)
- if err == nil {
- t.Fatal("Nonexisting blob did not return error")
- }
-
- ok, err := cli.DeleteBlobIfExists(cnt, blob)
- if err != nil {
- t.Fatalf("Not supposed to return error: %s", err)
- }
- if expected := false; ok != expected {
- t.Fatalf("Wrong deletion status. Expected: %v; Got: %v", expected, ok)
- }
-}
-
-func TestGetBlobProperties(t *testing.T) {
- cnt := randContainer()
- blob := randString(20)
- contents := randString(64)
-
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- err = cli.CreateContainer(cnt, ContainerAccessTypePrivate)
- if err != nil {
- t.Fatal(err)
- }
- defer cli.DeleteContainer(cnt)
-
- // Nonexisting blob
- _, err = cli.GetBlobProperties(cnt, blob)
- if err == nil {
- t.Fatal("Did not return error for non-existing blob")
- }
-
- // Put the blob
- err = cli.PutBlockBlob(cnt, blob, strings.NewReader(contents))
- if err != nil {
- t.Fatal(err)
- }
-
- // Get blob properties
- props, err := cli.GetBlobProperties(cnt, blob)
- if err != nil {
- t.Fatal(err)
- }
-
- if props.ContentLength != int64(len(contents)) {
- t.Fatalf("Got wrong Content-Length: '%d', expected: %d", props.ContentLength, len(contents))
- }
- if props.BlobType != BlobTypeBlock {
- t.Fatalf("Got wrong BlobType. Expected:'%s', got:'%s'", BlobTypeBlock, props.BlobType)
- }
-}
-
-func TestListBlobsPagination(t *testing.T) {
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- cnt := randContainer()
- err = cli.CreateContainer(cnt, ContainerAccessTypePrivate)
- if err != nil {
- t.Fatal(err)
- }
- defer cli.DeleteContainer(cnt)
-
- blobs := []string{}
- const n = 5
- const pageSize = 2
- for i := 0; i < n; i++ {
- name := randString(20)
- err := cli.PutBlockBlob(cnt, name, strings.NewReader("Hello, world!"))
- if err != nil {
- t.Fatal(err)
- }
- blobs = append(blobs, name)
- }
- sort.Strings(blobs)
-
- // Paginate
- seen := []string{}
- marker := ""
- for {
- resp, err := cli.ListBlobs(cnt, ListBlobsParameters{
- MaxResults: pageSize,
- Marker: marker})
- if err != nil {
- t.Fatal(err)
- }
-
- for _, v := range resp.Blobs {
- seen = append(seen, v.Name)
- }
-
- marker = resp.NextMarker
- if marker == "" || len(resp.Blobs) == 0 {
- break
- }
- }
-
- // Compare
- if !reflect.DeepEqual(blobs, seen) {
- t.Fatalf("Got wrong list of blobs. Expected: %s, Got: %s", blobs, seen)
- }
-
- err = cli.DeleteContainer(cnt)
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func TestPutEmptyBlockBlob(t *testing.T) {
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- cnt := randContainer()
- if err := cli.CreateContainer(cnt, ContainerAccessTypePrivate); err != nil {
- t.Fatal(err)
- }
- defer cli.deleteContainer(cnt)
-
- blob := randString(20)
- err = cli.PutBlockBlob(cnt, blob, bytes.NewReader([]byte{}))
- if err != nil {
- t.Fatal(err)
- }
-
- props, err := cli.GetBlobProperties(cnt, blob)
- if err != nil {
- t.Fatal(err)
- }
- if props.ContentLength != 0 {
- t.Fatalf("Wrong content length for empty blob: %d", props.ContentLength)
- }
-}
-
-func TestPutSingleBlockBlob(t *testing.T) {
- cnt := randContainer()
- blob := randString(20)
- body := []byte(randString(1024))
-
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- err = cli.CreateContainer(cnt, ContainerAccessTypeBlob)
- if err != nil {
- t.Fatal(err)
- }
- defer cli.DeleteContainer(cnt)
-
- err = cli.PutBlockBlob(cnt, blob, bytes.NewReader(body))
- if err != nil {
- t.Fatal(err)
- }
- defer cli.DeleteBlob(cnt, blob)
-
- resp, err := cli.GetBlob(cnt, blob)
- if err != nil {
- t.Fatal(err)
- }
-
- // Verify contents
- respBody, err := ioutil.ReadAll(resp)
- defer resp.Close()
- if err != nil {
- t.Fatal(err)
- }
-
- if !reflect.DeepEqual(body, respBody) {
- t.Fatalf("Wrong blob contents.\nExpected: %d bytes, Got: %d byes", len(body), len(respBody))
- }
-
- // Verify block list
- blocks, err := cli.GetBlockList(cnt, blob, BlockListTypeAll)
- if err != nil {
- t.Fatal(err)
- }
- if expected := 1; len(blocks.CommittedBlocks) != expected {
- t.Fatalf("Wrong committed block count. Expected: %d, Got: %d", expected, len(blocks.CommittedBlocks))
- }
- if expected := 0; len(blocks.UncommittedBlocks) != expected {
- t.Fatalf("Wrong unccommitted block count. Expected: %d, Got: %d", expected, len(blocks.UncommittedBlocks))
- }
- thatBlock := blocks.CommittedBlocks[0]
- if expected := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%011d", 0))); thatBlock.Name != expected {
- t.Fatalf("Wrong block name. Expected: %s, Got: %s", expected, thatBlock.Name)
- }
-}
-
-func TestGetBlobRange(t *testing.T) {
- cnt := randContainer()
- blob := randString(20)
- body := "0123456789"
-
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- err = cli.CreateContainer(cnt, ContainerAccessTypeBlob)
- if err != nil {
- t.Fatal(err)
- }
- defer cli.DeleteContainer(cnt)
-
- err = cli.PutBlockBlob(cnt, blob, strings.NewReader(body))
- if err != nil {
- t.Fatal(err)
- }
- defer cli.DeleteBlob(cnt, blob)
-
- // Read 1-3
- for _, r := range []struct {
- rangeStr string
- expected string
- }{
- {"0-", body},
- {"1-3", body[1 : 3+1]},
- {"3-", body[3:]},
- } {
- resp, err := cli.GetBlobRange(cnt, blob, r.rangeStr)
- if err != nil {
- t.Fatal(err)
- }
- blobBody, err := ioutil.ReadAll(resp)
- if err != nil {
- t.Fatal(err)
- }
- str := string(blobBody)
- if str != r.expected {
- t.Fatalf("Got wrong range. Expected: '%s'; Got:'%s'", r.expected, str)
- }
- }
-}
-
-func TestPutBlock(t *testing.T) {
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- cnt := randContainer()
- if err := cli.CreateContainer(cnt, ContainerAccessTypePrivate); err != nil {
- t.Fatal(err)
- }
- defer cli.deleteContainer(cnt)
-
- blob := randString(20)
- chunk := []byte(randString(1024))
- blockId := base64.StdEncoding.EncodeToString([]byte("foo"))
- err = cli.PutBlock(cnt, blob, blockId, chunk)
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func TestPutMultiBlockBlob(t *testing.T) {
- var (
- cnt = randContainer()
- blob = randString(20)
- blockSize = 32 * 1024 // 32 KB
- body = []byte(randString(blockSize*2 + blockSize/2)) // 3 blocks
- )
-
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- err = cli.CreateContainer(cnt, ContainerAccessTypeBlob)
- if err != nil {
- t.Fatal(err)
- }
- defer cli.DeleteContainer(cnt)
-
- err = cli.putBlockBlob(cnt, blob, bytes.NewReader(body), blockSize)
- if err != nil {
- t.Fatal(err)
- }
- defer cli.DeleteBlob(cnt, blob)
-
- resp, err := cli.GetBlob(cnt, blob)
- if err != nil {
- t.Fatal(err)
- }
-
- // Verify contents
- respBody, err := ioutil.ReadAll(resp)
- defer resp.Close()
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(body, respBody) {
- t.Fatalf("Wrong blob contents.\nExpected: %d bytes, Got: %d byes", len(body), len(respBody))
- }
-
- err = cli.DeleteBlob(cnt, blob)
- if err != nil {
- t.Fatal(err)
- }
-
- err = cli.DeleteContainer(cnt)
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func TestGetBlockList_PutBlockList(t *testing.T) {
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- cnt := randContainer()
- if err := cli.CreateContainer(cnt, ContainerAccessTypePrivate); err != nil {
- t.Fatal(err)
- }
- defer cli.deleteContainer(cnt)
-
- blob := randString(20)
- chunk := []byte(randString(1024))
- blockId := base64.StdEncoding.EncodeToString([]byte("foo"))
-
- // Put one block
- err = cli.PutBlock(cnt, blob, blockId, chunk)
- if err != nil {
- t.Fatal(err)
- }
- defer cli.deleteBlob(cnt, blob)
-
- // Get committed blocks
- committed, err := cli.GetBlockList(cnt, blob, BlockListTypeCommitted)
- if err != nil {
- t.Fatal(err)
- }
-
- if len(committed.CommittedBlocks) > 0 {
- t.Fatal("There are committed blocks")
- }
-
- // Get uncommitted blocks
- uncommitted, err := cli.GetBlockList(cnt, blob, BlockListTypeUncommitted)
- if err != nil {
- t.Fatal(err)
- }
-
- if expected := 1; len(uncommitted.UncommittedBlocks) != expected {
- t.Fatalf("Uncommitted blocks wrong. Expected: %d, got: %d", expected, len(uncommitted.UncommittedBlocks))
- }
-
- // Commit block list
- err = cli.PutBlockList(cnt, blob, []Block{{blockId, BlockStatusUncommitted}})
- if err != nil {
- t.Fatal(err)
- }
-
- // Get all blocks
- all, err := cli.GetBlockList(cnt, blob, BlockListTypeAll)
- if err != nil {
- t.Fatal(err)
- }
-
- if expected := 1; len(all.CommittedBlocks) != expected {
- t.Fatalf("Uncommitted blocks wrong. Expected: %d, got: %d", expected, len(uncommitted.CommittedBlocks))
- }
- if expected := 0; len(all.UncommittedBlocks) != expected {
- t.Fatalf("Uncommitted blocks wrong. Expected: %d, got: %d", expected, len(uncommitted.UncommittedBlocks))
- }
-
- // Verify the block
- thatBlock := all.CommittedBlocks[0]
- if expected := blockId; expected != thatBlock.Name {
- t.Fatalf("Wrong block name. Expected: %s, got: %s", expected, thatBlock.Name)
- }
- if expected := int64(len(chunk)); expected != thatBlock.Size {
- t.Fatalf("Wrong block name. Expected: %d, got: %d", expected, thatBlock.Size)
- }
-}
-
-func TestCreateBlockBlob(t *testing.T) {
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- cnt := randContainer()
- if err := cli.CreateContainer(cnt, ContainerAccessTypePrivate); err != nil {
- t.Fatal(err)
- }
- defer cli.deleteContainer(cnt)
-
- blob := randString(20)
- if err := cli.CreateBlockBlob(cnt, blob); err != nil {
- t.Fatal(err)
- }
-
- // Verify
- blocks, err := cli.GetBlockList(cnt, blob, BlockListTypeAll)
- if err != nil {
- t.Fatal(err)
- }
- if expected, got := 0, len(blocks.CommittedBlocks); expected != got {
- t.Fatalf("Got wrong committed block count. Expected: %v, Got:%v ", expected, got)
- }
- if expected, got := 0, len(blocks.UncommittedBlocks); expected != got {
- t.Fatalf("Got wrong uncommitted block count. Expected: %v, Got:%v ", expected, got)
- }
-}
-
-func TestPutPageBlob(t *testing.T) {
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- cnt := randContainer()
- if err := cli.CreateContainer(cnt, ContainerAccessTypePrivate); err != nil {
- t.Fatal(err)
- }
- defer cli.deleteContainer(cnt)
-
- blob := randString(20)
- size := int64(10 * 1024 * 1024)
- if err := cli.PutPageBlob(cnt, blob, size); err != nil {
- t.Fatal(err)
- }
-
- // Verify
- props, err := cli.GetBlobProperties(cnt, blob)
- if err != nil {
- t.Fatal(err)
- }
- if expected := size; expected != props.ContentLength {
- t.Fatalf("Got wrong Content-Length. Expected: %v, Got:%v ", expected, props.ContentLength)
- }
- if expected := BlobTypePage; expected != props.BlobType {
- t.Fatalf("Got wrong x-ms-blob-type. Expected: %v, Got:%v ", expected, props.BlobType)
- }
-}
-
-func TestPutPagesUpdate(t *testing.T) {
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- cnt := randContainer()
- if err := cli.CreateContainer(cnt, ContainerAccessTypePrivate); err != nil {
- t.Fatal(err)
- }
- defer cli.deleteContainer(cnt)
-
- blob := randString(20)
- size := int64(10 * 1024 * 1024) // larger than we'll use
- if err := cli.PutPageBlob(cnt, blob, size); err != nil {
- t.Fatal(err)
- }
-
- chunk1 := []byte(randString(1024))
- chunk2 := []byte(randString(512))
- // Append chunks
- if err := cli.PutPage(cnt, blob, 0, int64(len(chunk1)-1), PageWriteTypeUpdate, chunk1); err != nil {
- t.Fatal(err)
- }
- if err := cli.PutPage(cnt, blob, int64(len(chunk1)), int64(len(chunk1)+len(chunk2)-1), PageWriteTypeUpdate, chunk2); err != nil {
- t.Fatal(err)
- }
-
- // Verify contents
- out, err := cli.GetBlobRange(cnt, blob, fmt.Sprintf("%v-%v", 0, len(chunk1)+len(chunk2)))
- if err != nil {
- t.Fatal(err)
- }
- blobContents, err := ioutil.ReadAll(out)
- defer out.Close()
- if err != nil {
- t.Fatal(err)
- }
- if expected := append(chunk1, chunk2...); reflect.DeepEqual(blobContents, expected) {
- t.Fatalf("Got wrong blob.\nGot:%d bytes, Expected:%d bytes", len(blobContents), len(expected))
- }
- out.Close()
-
- // Overwrite first half of chunk1
- chunk0 := []byte(randString(512))
- if err := cli.PutPage(cnt, blob, 0, int64(len(chunk0)-1), PageWriteTypeUpdate, chunk0); err != nil {
- t.Fatal(err)
- }
-
- // Verify contents
- out, err = cli.GetBlobRange(cnt, blob, fmt.Sprintf("%v-%v", 0, len(chunk1)+len(chunk2)))
- if err != nil {
- t.Fatal(err)
- }
- blobContents, err = ioutil.ReadAll(out)
- defer out.Close()
- if err != nil {
- t.Fatal(err)
- }
- if expected := append(append(chunk0, chunk1[512:]...), chunk2...); reflect.DeepEqual(blobContents, expected) {
- t.Fatalf("Got wrong blob.\nGot:%d bytes, Expected:%d bytes", len(blobContents), len(expected))
- }
-}
-
-func TestPutPagesClear(t *testing.T) {
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- cnt := randContainer()
- if err := cli.CreateContainer(cnt, ContainerAccessTypePrivate); err != nil {
- t.Fatal(err)
- }
- defer cli.deleteContainer(cnt)
-
- blob := randString(20)
- size := int64(10 * 1024 * 1024) // larger than we'll use
-
- if err := cli.PutPageBlob(cnt, blob, size); err != nil {
- t.Fatal(err)
- }
-
- // Put 0-2047
- chunk := []byte(randString(2048))
- if err := cli.PutPage(cnt, blob, 0, 2047, PageWriteTypeUpdate, chunk); err != nil {
- t.Fatal(err)
- }
-
- // Clear 512-1023
- if err := cli.PutPage(cnt, blob, 512, 1023, PageWriteTypeClear, nil); err != nil {
- t.Fatal(err)
- }
-
- // Get blob contents
- if out, err := cli.GetBlobRange(cnt, blob, "0-2048"); err != nil {
- t.Fatal(err)
- } else {
- contents, err := ioutil.ReadAll(out)
- defer out.Close()
- if err != nil {
- t.Fatal(err)
- }
-
- if expected := append(append(chunk[:512], make([]byte, 512)...), chunk[1024:]...); reflect.DeepEqual(contents, expected) {
- t.Fatalf("Cleared blob is not the same. Expected: (%d) %v; got: (%d) %v", len(expected), expected, len(contents), contents)
- }
- }
-}
-
-func TestGetPageRanges(t *testing.T) {
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- cnt := randContainer()
- if err := cli.CreateContainer(cnt, ContainerAccessTypePrivate); err != nil {
- t.Fatal(err)
- }
- defer cli.deleteContainer(cnt)
-
- blob := randString(20)
- size := int64(10 * 1024 * 1024) // larger than we'll use
-
- if err := cli.PutPageBlob(cnt, blob, size); err != nil {
- t.Fatal(err)
- }
-
- // Get page ranges on empty blob
- if out, err := cli.GetPageRanges(cnt, blob); err != nil {
- t.Fatal(err)
- } else if len(out.PageList) != 0 {
- t.Fatal("Blob has pages")
- }
-
- // Add 0-512 page
- err = cli.PutPage(cnt, blob, 0, 511, PageWriteTypeUpdate, []byte(randString(512)))
- if err != nil {
- t.Fatal(err)
- }
-
- if out, err := cli.GetPageRanges(cnt, blob); err != nil {
- t.Fatal(err)
- } else if expected := 1; len(out.PageList) != expected {
- t.Fatalf("Expected %d pages, got: %d -- %v", expected, len(out.PageList), out.PageList)
- }
-
- // Add 1024-2048
- err = cli.PutPage(cnt, blob, 1024, 2047, PageWriteTypeUpdate, []byte(randString(1024)))
- if err != nil {
- t.Fatal(err)
- }
-
- if out, err := cli.GetPageRanges(cnt, blob); err != nil {
- t.Fatal(err)
- } else if expected := 2; len(out.PageList) != expected {
- t.Fatalf("Expected %d pages, got: %d -- %v", expected, len(out.PageList), out.PageList)
- }
-}
-
-func deleteTestContainers(cli *BlobStorageClient) error {
- for {
- resp, err := cli.ListContainers(ListContainersParameters{Prefix: testContainerPrefix})
- if err != nil {
- return err
- }
- if len(resp.Containers) == 0 {
- break
- }
- for _, c := range resp.Containers {
- err = cli.DeleteContainer(c.Name)
- if err != nil {
- return err
- }
- }
- }
- return nil
-}
-
-func getBlobClient() (*BlobStorageClient, error) {
- name := os.Getenv("ACCOUNT_NAME")
- if name == "" {
- return nil, errors.New("ACCOUNT_NAME not set, need an empty storage account to test")
- }
- key := os.Getenv("ACCOUNT_KEY")
- if key == "" {
- return nil, errors.New("ACCOUNT_KEY not set")
- }
- cli, err := NewBasicClient(name, key)
- if err != nil {
- return nil, err
- }
- return cli.GetBlobService(), nil
-}
-
-func randContainer() string {
- return testContainerPrefix + randString(32-len(testContainerPrefix))
-}
-
-func randString(n int) string {
- if n <= 0 {
- panic("negative number")
- }
- const alphanum = "0123456789abcdefghijklmnopqrstuvwxyz"
- var bytes = make([]byte, n)
- rand.Read(bytes)
- for i, b := range bytes {
- bytes[i] = alphanum[b%byte(len(alphanum))]
- }
- return string(bytes)
-}
diff --git a/Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/storage/client_test.go b/Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/storage/client_test.go
deleted file mode 100644
index 844962a60..000000000
--- a/Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/storage/client_test.go
+++ /dev/null
@@ -1,203 +0,0 @@
-package storage
-
-import (
- "encoding/base64"
- "net/url"
- "testing"
-)
-
-func TestGetBaseUrl_Basic_Https(t *testing.T) {
- cli, err := NewBasicClient("foo", "YmFy")
- if err != nil {
- t.Fatal(err)
- }
-
- if cli.apiVersion != DefaultApiVersion {
- t.Fatalf("Wrong api version. Expected: '%s', got: '%s'", DefaultApiVersion, cli.apiVersion)
- }
-
- if err != nil {
- t.Fatal(err)
- }
- output := cli.getBaseUrl("table")
-
- if expected := "https://foo.table.core.windows.net"; output != expected {
- t.Fatalf("Wrong base url. Expected: '%s', got: '%s'", expected, output)
- }
-}
-
-func TestGetBaseUrl_Custom_NoHttps(t *testing.T) {
- apiVersion := DefaultApiVersion
- cli, err := NewClient("foo", "YmFy", "core.chinacloudapi.cn", apiVersion, false)
- if err != nil {
- t.Fatal(err)
- }
-
- if cli.apiVersion != apiVersion {
- t.Fatalf("Wrong api version. Expected: '%s', got: '%s'", apiVersion, cli.apiVersion)
- }
-
- output := cli.getBaseUrl("table")
-
- if expected := "http://foo.table.core.chinacloudapi.cn"; output != expected {
- t.Fatalf("Wrong base url. Expected: '%s', got: '%s'", expected, output)
- }
-}
-
-func TestGetEndpoint_None(t *testing.T) {
- cli, err := NewBasicClient("foo", "YmFy")
- if err != nil {
- t.Fatal(err)
- }
- output := cli.getEndpoint(blobServiceName, "", url.Values{})
-
- if expected := "https://foo.blob.core.windows.net/"; output != expected {
- t.Fatalf("Wrong endpoint url. Expected: '%s', got: '%s'", expected, output)
- }
-}
-
-func TestGetEndpoint_PathOnly(t *testing.T) {
- cli, err := NewBasicClient("foo", "YmFy")
- if err != nil {
- t.Fatal(err)
- }
- output := cli.getEndpoint(blobServiceName, "path", url.Values{})
-
- if expected := "https://foo.blob.core.windows.net/path"; output != expected {
- t.Fatalf("Wrong endpoint url. Expected: '%s', got: '%s'", expected, output)
- }
-}
-
-func TestGetEndpoint_ParamsOnly(t *testing.T) {
- cli, err := NewBasicClient("foo", "YmFy")
- if err != nil {
- t.Fatal(err)
- }
- params := url.Values{}
- params.Set("a", "b")
- params.Set("c", "d")
- output := cli.getEndpoint(blobServiceName, "", params)
-
- if expected := "https://foo.blob.core.windows.net/?a=b&c=d"; output != expected {
- t.Fatalf("Wrong endpoint url. Expected: '%s', got: '%s'", expected, output)
- }
-}
-
-func TestGetEndpoint_Mixed(t *testing.T) {
- cli, err := NewBasicClient("foo", "YmFy")
- if err != nil {
- t.Fatal(err)
- }
- params := url.Values{}
- params.Set("a", "b")
- params.Set("c", "d")
- output := cli.getEndpoint(blobServiceName, "path", params)
-
- if expected := "https://foo.blob.core.windows.net/path?a=b&c=d"; output != expected {
- t.Fatalf("Wrong endpoint url. Expected: '%s', got: '%s'", expected, output)
- }
-}
-
-func Test_getStandardHeaders(t *testing.T) {
- cli, err := NewBasicClient("foo", "YmFy")
- if err != nil {
- t.Fatal(err)
- }
-
- headers := cli.getStandardHeaders()
- if len(headers) != 2 {
- t.Fatal("Wrong standard header count")
- }
- if v, ok := headers["x-ms-version"]; !ok || v != cli.apiVersion {
- t.Fatal("Wrong version header")
- }
- if _, ok := headers["x-ms-date"]; !ok {
- t.Fatal("Missing date header")
- }
-}
-
-func Test_buildCanonicalizedResource(t *testing.T) {
- cli, err := NewBasicClient("foo", "YmFy")
- if err != nil {
- t.Fatal(err)
- }
-
- type test struct{ url, expected string }
- tests := []test{
- {"https://foo.blob.core.windows.net/path?a=b&c=d", "/foo/path\na:b\nc:d"},
- {"https://foo.blob.core.windows.net/?comp=list", "/foo/\ncomp:list"},
- {"https://foo.blob.core.windows.net/cnt/blob", "/foo/cnt/blob"},
- }
-
- for _, i := range tests {
- if out, err := cli.buildCanonicalizedResource(i.url); err != nil {
- t.Fatal(err)
- } else if out != i.expected {
- t.Fatalf("Wrong canonicalized resource. Expected:\n'%s', Got:\n'%s'", i.expected, out)
- }
- }
-}
-
-func Test_buildCanonicalizedHeader(t *testing.T) {
- cli, err := NewBasicClient("foo", "YmFy")
- if err != nil {
- t.Fatal(err)
- }
-
- type test struct {
- headers map[string]string
- expected string
- }
- tests := []test{
- {map[string]string{}, ""},
- {map[string]string{"x-ms-foo": "bar"}, "x-ms-foo:bar"},
- {map[string]string{"foo:": "bar"}, ""},
- {map[string]string{"foo:": "bar", "x-ms-foo": "bar"}, "x-ms-foo:bar"},
- {map[string]string{
- "x-ms-version": "9999-99-99",
- "x-ms-blob-type": "BlockBlob"}, "x-ms-blob-type:BlockBlob\nx-ms-version:9999-99-99"}}
-
- for _, i := range tests {
- if out := cli.buildCanonicalizedHeader(i.headers); out != i.expected {
- t.Fatalf("Wrong canonicalized resource. Expected:\n'%s', Got:\n'%s'", i.expected, out)
- }
- }
-}
-
-func TestReturnsStorageServiceError(t *testing.T) {
- cli, err := getBlobClient()
- if err != nil {
- t.Fatal(err)
- }
-
- // attempt to delete a nonexisting container
- _, err = cli.deleteContainer(randContainer())
- if err == nil {
- t.Fatal("Service has not returned an error")
- }
-
- if v, ok := err.(StorageServiceError); !ok {
- t.Fatal("Cannot assert to specific error")
- } else if v.StatusCode != 404 {
- t.Fatalf("Expected status:%d, got: %d", 404, v.StatusCode)
- } else if v.Code != "ContainerNotFound" {
- t.Fatalf("Expected code: %s, got: %s", "ContainerNotFound", v.Code)
- } else if v.RequestId == "" {
- t.Fatalf("RequestId does not exist")
- }
-}
-
-func Test_createAuthorizationHeader(t *testing.T) {
- key := base64.StdEncoding.EncodeToString([]byte("bar"))
- cli, err := NewBasicClient("foo", key)
- if err != nil {
- t.Fatal(err)
- }
-
- canonicalizedString := `foobarzoo`
- expected := `SharedKey foo:h5U0ATVX6SpbFX1H6GNuxIMeXXCILLoIvhflPtuQZ30=`
-
- if out := cli.createAuthorizationHeader(canonicalizedString); out != expected {
- t.Fatalf("Wrong authorization header. Expected: '%s', Got:'%s'", expected, out)
- }
-}
diff --git a/Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/storage/util_test.go b/Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/storage/util_test.go
deleted file mode 100644
index d1b2c7949..000000000
--- a/Godeps/_workspace/src/github.com/MSOpenTech/azure-sdk-for-go/storage/util_test.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package storage
-
-import (
- "io/ioutil"
- "net/url"
- "reflect"
- "strings"
- "testing"
- "time"
-)
-
-func Test_timeRfc1123Formatted(t *testing.T) {
- now := time.Now().UTC()
-
- expectedLayout := "Mon, 02 Jan 2006 15:04:05 GMT"
- expected := now.Format(expectedLayout)
-
- if output := timeRfc1123Formatted(now); output != expected {
- t.Errorf("Expected: %s, got: %s", expected, output)
- }
-}
-
-func Test_mergeParams(t *testing.T) {
- v1 := url.Values{
- "k1": {"v1"},
- "k2": {"v2"}}
- v2 := url.Values{
- "k1": {"v11"},
- "k3": {"v3"}}
-
- out := mergeParams(v1, v2)
- if v := out.Get("k1"); v != "v1" {
- t.Errorf("Wrong value for k1: %s", v)
- }
-
- if v := out.Get("k2"); v != "v2" {
- t.Errorf("Wrong value for k2: %s", v)
- }
-
- if v := out.Get("k3"); v != "v3" {
- t.Errorf("Wrong value for k3: %s", v)
- }
-
- if v := out["k1"]; !reflect.DeepEqual(v, []string{"v1", "v11"}) {
- t.Errorf("Wrong multi-value for k1: %s", v)
- }
-}
-
-func Test_prepareBlockListRequest(t *testing.T) {
- empty := []Block{}
- expected := ``
- if out := prepareBlockListRequest(empty); expected != out {
- t.Errorf("Wrong block list. Expected: '%s', got: '%s'", expected, out)
- }
-
- blocks := []Block{{"foo", BlockStatusLatest}, {"bar", BlockStatusUncommitted}}
- expected = `foobar`
- if out := prepareBlockListRequest(blocks); expected != out {
- t.Errorf("Wrong block list. Expected: '%s', got: '%s'", expected, out)
- }
-}
-
-func Test_xmlUnmarshal(t *testing.T) {
- xml := `
-
- myblob
- `
-
- body := ioutil.NopCloser(strings.NewReader(xml))
-
- var blob Blob
- err := xmlUnmarshal(body, &blob)
- if err != nil {
- t.Fatal(err)
- }
-
- if blob.Name != "myblob" {
- t.Fatal("Got wrong value")
- }
-}
diff --git a/cmd/registry/main.go b/cmd/registry/main.go
index 49132bf1b..df1359172 100644
--- a/cmd/registry/main.go
+++ b/cmd/registry/main.go
@@ -22,6 +22,7 @@ import (
_ "github.com/docker/distribution/registry/auth/token"
"github.com/docker/distribution/registry/handlers"
"github.com/docker/distribution/registry/listener"
+ _ "github.com/docker/distribution/registry/storage/driver/azure"
_ "github.com/docker/distribution/registry/storage/driver/filesystem"
_ "github.com/docker/distribution/registry/storage/driver/inmemory"
_ "github.com/docker/distribution/registry/storage/driver/middleware/cloudfront"
diff --git a/docs/building.md b/docs/building.md
index cd534c715..5c40e0eb7 100644
--- a/docs/building.md
+++ b/docs/building.md
@@ -139,8 +139,3 @@ To enable the [Ceph RADOS storage driver](storage-drivers/rados.md)
```sh
export DOCKER_BUILDTAGS='include_rados'
```
-
-To enable the [Azure storage driver](storage-drivers/azure.md), use the
-`include_azure` build tag.
-
-
diff --git a/docs/storage-drivers/azure.md b/docs/storage-drivers/azure.md
index 01cba5c37..f0c16f04e 100644
--- a/docs/storage-drivers/azure.md
+++ b/docs/storage-drivers/azure.md
@@ -17,11 +17,5 @@ The following parameters must be used to authenticate and configure the storage
* `container`: Name of the root storage container in which all registry data will be stored. Must comply the storage container name [requirements][create-container-api].
* `realm`: (optional) Domain name suffix for the Storage Service API endpoint. Defaults to `core.windows.net`. For example realm for "Azure in China" would be `core.chinacloudapi.cn` and realm for "Azure Government" would be `core.usgovcloudapi.net`.
-## Developing
-
-To include this driver when building Docker Distribution, use the build tag
-`include_azure`. Please see the [building documentation][building] for details.
-
[azure-blob-storage]: http://azure.microsoft.com/en-us/services/storage/
[create-container-api]: https://msdn.microsoft.com/en-us/library/azure/dd179468.aspx
-[building]: https://github.com/docker/distribution/blob/master/docs/building.md#optional-build-tags
diff --git a/registry/storage/driver/azure/azure.go b/registry/storage/driver/azure/azure.go
index d21a8259b..cbb959812 100644
--- a/registry/storage/driver/azure/azure.go
+++ b/registry/storage/driver/azure/azure.go
@@ -16,7 +16,7 @@ import (
"github.com/docker/distribution/registry/storage/driver/base"
"github.com/docker/distribution/registry/storage/driver/factory"
- azure "github.com/MSOpenTech/azure-sdk-for-go/storage"
+ azure "github.com/Azure/azure-sdk-for-go/storage"
)
const driverName = "azure"
@@ -68,7 +68,7 @@ func FromParameters(parameters map[string]interface{}) (*Driver, error) {
realm, ok := parameters[paramRealm]
if !ok || fmt.Sprint(realm) == "" {
- realm = azure.DefaultBaseUrl
+ realm = azure.DefaultBaseURL
}
return New(fmt.Sprint(accountName), fmt.Sprint(accountKey), fmt.Sprint(container), fmt.Sprint(realm))
@@ -76,7 +76,7 @@ func FromParameters(parameters map[string]interface{}) (*Driver, error) {
// New constructs a new Driver with the given Azure Storage Account credentials
func New(accountName, accountKey, container, realm string) (*Driver, error) {
- api, err := azure.NewClient(accountName, accountKey, realm, azure.DefaultApiVersion, true)
+ api, err := azure.NewClient(accountName, accountKey, realm, azure.DefaultAPIVersion, true)
if err != nil {
return nil, err
}
@@ -89,7 +89,7 @@ func New(accountName, accountKey, container, realm string) (*Driver, error) {
}
d := &driver{
- client: *blobClient,
+ client: blobClient,
container: container}
return &Driver{baseEmbed: baseEmbed{Base: base.Base{StorageDriver: d}}}, nil
}
@@ -114,7 +114,16 @@ func (d *driver) GetContent(ctx context.Context, path string) ([]byte, error) {
// PutContent stores the []byte content at a location designated by "path".
func (d *driver) PutContent(ctx context.Context, path string, contents []byte) error {
- return d.client.PutBlockBlob(d.container, path, ioutil.NopCloser(bytes.NewReader(contents)))
+ if _, err := d.client.DeleteBlobIfExists(d.container, path); err != nil {
+ return err
+ }
+ if err := d.client.CreateBlockBlob(d.container, path); err != nil {
+ return err
+ }
+ bs := newAzureBlockStorage(d.client)
+ bw := newRandomBlobWriter(&bs, azure.MaxBlobBlockSize)
+ _, err := bw.WriteBlobAt(d.container, path, 0, bytes.NewReader(contents))
+ return err
}
// ReadStream retrieves an io.ReadCloser for the content stored at "path" with a
@@ -233,7 +242,7 @@ func (d *driver) List(ctx context.Context, path string) ([]string, error) {
// Move moves an object stored at sourcePath to destPath, removing the original
// object.
func (d *driver) Move(ctx context.Context, sourcePath string, destPath string) error {
- sourceBlobURL := d.client.GetBlobUrl(d.container, sourcePath)
+ sourceBlobURL := d.client.GetBlobURL(d.container, sourcePath)
err := d.client.CopyBlob(d.container, destPath, sourceBlobURL)
if err != nil {
if is404(err) {
@@ -352,6 +361,6 @@ func (d *driver) listBlobs(container, virtPath string) ([]string, error) {
}
func is404(err error) bool {
- e, ok := err.(azure.StorageServiceError)
+ e, ok := err.(azure.AzureStorageServiceError)
return ok && e.StatusCode == http.StatusNotFound
}
diff --git a/registry/storage/driver/azure/blockblob.go b/registry/storage/driver/azure/blockblob.go
index 10b2bf216..1c1df899c 100644
--- a/registry/storage/driver/azure/blockblob.go
+++ b/registry/storage/driver/azure/blockblob.go
@@ -4,7 +4,7 @@ import (
"fmt"
"io"
- azure "github.com/MSOpenTech/azure-sdk-for-go/storage"
+ azure "github.com/Azure/azure-sdk-for-go/storage"
)
// azureBlockStorage is adaptor between azure.BlobStorageClient and
diff --git a/registry/storage/driver/azure/blockblob_test.go b/registry/storage/driver/azure/blockblob_test.go
index c29b4742c..7ce471957 100644
--- a/registry/storage/driver/azure/blockblob_test.go
+++ b/registry/storage/driver/azure/blockblob_test.go
@@ -6,7 +6,7 @@ import (
"io"
"io/ioutil"
- azure "github.com/MSOpenTech/azure-sdk-for-go/storage"
+ azure "github.com/Azure/azure-sdk-for-go/storage"
)
type StorageSimulator struct {
@@ -122,12 +122,12 @@ func (s *StorageSimulator) PutBlockList(container, blob string, blocks []azure.B
var blockIDs []string
for _, v := range blocks {
- bl, ok := bb.blocks[v.Id]
+ bl, ok := bb.blocks[v.ID]
if !ok { // check if block ID exists
- return fmt.Errorf("Block id '%s' not found", v.Id)
+ return fmt.Errorf("Block id '%s' not found", v.ID)
}
bl.committed = true
- blockIDs = append(blockIDs, v.Id)
+ blockIDs = append(blockIDs, v.ID)
}
// Mark all other blocks uncommitted
diff --git a/registry/storage/driver/azure/blockid.go b/registry/storage/driver/azure/blockid.go
index f6bda6a86..776c7cd59 100644
--- a/registry/storage/driver/azure/blockid.go
+++ b/registry/storage/driver/azure/blockid.go
@@ -7,7 +7,7 @@ import (
"sync"
"time"
- azure "github.com/MSOpenTech/azure-sdk-for-go/storage"
+ azure "github.com/Azure/azure-sdk-for-go/storage"
)
type blockIDGenerator struct {
diff --git a/registry/storage/driver/azure/blockid_test.go b/registry/storage/driver/azure/blockid_test.go
index 6569e15d7..aab70202a 100644
--- a/registry/storage/driver/azure/blockid_test.go
+++ b/registry/storage/driver/azure/blockid_test.go
@@ -4,7 +4,7 @@ import (
"math"
"testing"
- azure "github.com/MSOpenTech/azure-sdk-for-go/storage"
+ azure "github.com/Azure/azure-sdk-for-go/storage"
)
func Test_blockIdGenerator(t *testing.T) {
diff --git a/registry/storage/driver/azure/randomwriter.go b/registry/storage/driver/azure/randomwriter.go
index b570d5593..f18692d0b 100644
--- a/registry/storage/driver/azure/randomwriter.go
+++ b/registry/storage/driver/azure/randomwriter.go
@@ -5,7 +5,7 @@ import (
"io"
"io/ioutil"
- azure "github.com/MSOpenTech/azure-sdk-for-go/storage"
+ azure "github.com/Azure/azure-sdk-for-go/storage"
)
// blockStorage is the interface required from a block storage service
@@ -75,7 +75,7 @@ func (r *randomBlobWriter) WriteBlobAt(container, blob string, offset int64, chu
// Use existing block list
var existingBlocks []azure.Block
for _, v := range blocks.CommittedBlocks {
- existingBlocks = append(existingBlocks, azure.Block{Id: v.Name, Status: azure.BlockStatusCommitted})
+ existingBlocks = append(existingBlocks, azure.Block{ID: v.Name, Status: azure.BlockStatusCommitted})
}
blockList = append(existingBlocks, blockList...)
}
@@ -111,7 +111,7 @@ func (r *randomBlobWriter) writeChunkToBlocks(container, blob string, chunk io.R
if err := r.bs.PutBlock(container, blob, blockID, data); err != nil {
return newBlocks, nn, err
}
- newBlocks = append(newBlocks, azure.Block{Id: blockID, Status: azure.BlockStatusUncommitted})
+ newBlocks = append(newBlocks, azure.Block{ID: blockID, Status: azure.BlockStatusUncommitted})
}
return newBlocks, nn, nil
}
@@ -131,7 +131,7 @@ func (r *randomBlobWriter) blocksLeftSide(container, blob string, writeOffset in
for _, v := range bx.CommittedBlocks {
blkSize := int64(v.Size)
if o >= blkSize { // use existing block
- left = append(left, azure.Block{Id: v.Name, Status: azure.BlockStatusCommitted})
+ left = append(left, azure.Block{ID: v.Name, Status: azure.BlockStatusCommitted})
o -= blkSize
elapsed += blkSize
} else if o > 0 { // current block needs to be splitted
@@ -150,7 +150,7 @@ func (r *randomBlobWriter) blocksLeftSide(container, blob string, writeOffset in
if err = r.bs.PutBlock(container, blob, newBlockID, data); err != nil {
return left, err
}
- left = append(left, azure.Block{Id: newBlockID, Status: azure.BlockStatusUncommitted})
+ left = append(left, azure.Block{ID: newBlockID, Status: azure.BlockStatusUncommitted})
break
}
}
@@ -177,7 +177,7 @@ func (r *randomBlobWriter) blocksRightSide(container, blob string, writeOffset i
)
if bs > re { // take the block as is
- right = append(right, azure.Block{Id: v.Name, Status: azure.BlockStatusCommitted})
+ right = append(right, azure.Block{ID: v.Name, Status: azure.BlockStatusCommitted})
} else if be > re { // current block needs to be splitted
part, err := r.bs.GetSectionReader(container, blob, re+1, be-(re+1)+1)
if err != nil {
@@ -192,7 +192,7 @@ func (r *randomBlobWriter) blocksRightSide(container, blob string, writeOffset i
if err = r.bs.PutBlock(container, blob, newBlockID, data); err != nil {
return right, err
}
- right = append(right, azure.Block{Id: newBlockID, Status: azure.BlockStatusUncommitted})
+ right = append(right, azure.Block{ID: newBlockID, Status: azure.BlockStatusUncommitted})
}
elapsed += int64(v.Size)
}
diff --git a/registry/storage/driver/azure/randomwriter_test.go b/registry/storage/driver/azure/randomwriter_test.go
index 2c7480dbf..32c2509e4 100644
--- a/registry/storage/driver/azure/randomwriter_test.go
+++ b/registry/storage/driver/azure/randomwriter_test.go
@@ -9,7 +9,7 @@ import (
"strings"
"testing"
- azure "github.com/MSOpenTech/azure-sdk-for-go/storage"
+ azure "github.com/Azure/azure-sdk-for-go/storage"
)
func TestRandomWriter_writeChunkToBlocks(t *testing.T) {