diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..787012d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+vendor/
+.idea/
+example/example
+.DS_Store
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..b8c3989
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+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/README.md b/README.md
new file mode 100644
index 0000000..8776246
--- /dev/null
+++ b/README.md
@@ -0,0 +1,89 @@
+# Giovanni
+An alternative Azure Storage SDK for Go
+
+---
+
+This repository is an alternative Azure Storage SDK for Go; which supports for:
+
+- The [Blob Storage API's](https://docs.microsoft.com/en-us/rest/api/storageservices/blob-service-rest-api)
+- The [File Storage API's](https://docs.microsoft.com/en-us/rest/api/storageservices/file-service-rest-api)
+- The [Queue Storage API's](https://docs.microsoft.com/en-us/rest/api/storageservices/queue-service-rest-api)
+- The [Table Storage API's](https://docs.microsoft.com/en-us/rest/api/storageservices/table-service-rest-api)
+
+At this time we support the following API Versions:
+
+* `2018-11-09` (`./storage/2018-11-09`)
+* `2018-03-28` (`./storage/2018-03-28`)
+* `2017-07-29` (`./storage/2017-07-29`)
+
+We're also open to supporting other versions of the Azure Storage SDK as necessary.
+
+Documentation for how to use each SDK can be found within the README for that SDK version - for example [here's the README for 2018-11-09](storage/2018-11-09/README.md).
+
+Each Package also contains Unit and Acceptance tests to ensure that the functionality works; instructions on how to run the tests can be found below.
+
+## Mission Statement
+
+Fundamentally: developers should be able to pick which version of the Azure API they target using this SDK.
+
+As such, there's two main goals here:
+
+* New API Versions will be added additively to the `storage` folder.
+
+* Any supported API Versions will continue to exist in the `storage` folder until they're EOL'd/stop working.
+
+To ensure that each of these scenarios is possible - we have Acceptance and Unit Tests to confirm that the functionality in these versions works - and will use SemVer as appropriate.
+
+## Future Enhancements
+
+At this time this SDK is mostly feature complete, with a couple of notable additions (since we didn't need them).
+
+Whilst it's possible to create Snapshots (for example, of a Container) - at this time most SDK calls don't support specifying the optional query-string value for `snapshot`.
+
+In addition, we also don't support the `timeout` querystring on every API call; this is because instead all SDK methods take a `context` object, which allows a timeout to be set (albeit on the Client rather than the Remote API Call).
+
+In both instances this is because we didn't need this functionality for our use-cases - but feel free to send a PR if you need this.
+
+## Licence
+
+Apache 2.0
+
+## Technical Implementation
+
+This SDK makes use of the standard Preparer-Sender-Responder pattern found in [Azure/go-autorest](https://github.com/Azure/go-autorest) - which means that this SDK should be familiar and compatible with [the Azure SDK for Go](https://github.com/Azure/azure-sdk-for-go).
+
+Depending on the API Version / API being used - different authentication mechanisms are possible (see the README within the specific SDK for more info ([example](XXX)). In all cases one of the following Authorizers will be required:
+
+* An Authorizer for Azure Active Directory
+* A SharedKeyLite Authorizer (for Blob, Queue and Table Storage)
+* A SharedKeyLite Authorizer (for Table Storage)
+
+Examples for all of these can be found below in [the Examples Directory](examples/).
+
+## Running the Tests
+
+Each package contains both Unit and Acceptance Tests which provision a real Storage Account on Azure, and then run tests against that.
+
+To run those, the following Environment Variables need to be set:
+
+* `ARM_SUBSCRIPTION_ID` - The ID of the Subscription where tests should be run, such as `00000000-0000-0000-0000-000000000000`.
+* `ARM_CLIENT_ID` - The ID of the AzureAD Application (also known as a Client ID), such as `00000000-0000-0000-0000-000000000000`.
+* `ARM_CLIENT_SECRET` - The Client Secret/Password for a Service Principal where tests should be run.
+* `ARM_ENVIRONMENT` - The name of the Azure Environment where the tests should be run, such as `Public` or `Germany`.
+* `ARM_TEST_LOCATION` - The name of the Azure Region where resources provisioned by the tests should be created, such as `West Europe`.
+
+Once those Environment Variables are set - you should be able to run:
+
+```bash
+$ go test -v ./storage/...
+```
+
+You can also run them for a specific API version by running:
+
+```bash
+$ go test -v ./storage/2018-11-09/...
+```
+
+## Debugging
+
+You can see the Requests/Responses from this SDK by setting the Environment Variable `TEST_LOG` to any value.
diff --git a/example/azuread-auth/README.md b/example/azuread-auth/README.md
new file mode 100644
index 0000000..1e6b33b
--- /dev/null
+++ b/example/azuread-auth/README.md
@@ -0,0 +1,19 @@
+## Example: using Azure Active Directory authentication
+
+This example provisions a Storage Container using Azure Active Directory for authentication.
+
+To run this example you need the following Environment Variables set:
+
+* `ARM_CLIENT_ID` - The UUID of the Service Principal/Application
+* `ARM_CLIENT_SECRET` - The Secret associated with the Service Principal
+* `ARM_ENVIRONMENT` - The Azure Environment (`public`, `germany` etc)
+* `ARM_SUBSCRIPTION_ID` - The UUID of the Azure Subscription
+* `ARM_TENANT_ID` - The UUID of the Azure Tenant
+
+You also need to update `main.go` to set the variable `storageAccountName` to an existing Storage Account (since we don't provision one for you).
+
+Assuming you've got Go installed - you can then run this using:
+
+```bash
+$ go run main.go
+```
\ No newline at end of file
diff --git a/example/azuread-auth/main.go b/example/azuread-auth/main.go
new file mode 100644
index 0000000..e72537c
--- /dev/null
+++ b/example/azuread-auth/main.go
@@ -0,0 +1,109 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "net/http"
+ "os"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/adal"
+ "github.com/hashicorp/go-azure-helpers/authentication"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers"
+)
+
+func main() {
+ log.Printf("[DEBUG] Started..")
+
+ // NOTE: fill this in
+ storageAccountName := "example"
+
+ log.Printf("[DEBUG] Building Client..")
+ client, err := buildClient()
+ if err != nil {
+ panic(fmt.Errorf("Error building client: %s", err))
+ }
+
+ ctx := context.TODO()
+ containerName := "armauth"
+ input := containers.CreateInput{
+ AccessLevel: containers.Private,
+ MetaData: map[string]string{
+ "hello": "world",
+ },
+ }
+ log.Printf("[DEBUG] Creating Container..")
+ if _, err := client.ContainersClient.Create(ctx, storageAccountName, containerName, input); err != nil {
+ panic(fmt.Errorf("Error creating container: %s", err))
+ }
+
+ log.Printf("[DEBUG] Retrieving Container..")
+ container, err := client.ContainersClient.GetProperties(ctx, storageAccountName, containerName)
+ if err != nil {
+ panic(fmt.Errorf("Error reading properties for container: %s", err))
+ }
+
+ log.Printf("[DEBUG] MetaData: %+v", container.MetaData)
+}
+
+type Client struct {
+ ContainersClient containers.Client
+}
+
+func buildClient() (*Client, error) {
+ // we're using github.com/hashicorp/go-azure-helpers since it makes this simpler
+ // but you can use an Authorizer from github.com/Azure/go-autorest directly too
+ builder := &authentication.Builder{
+ SubscriptionID: os.Getenv("ARM_SUBSCRIPTION_ID"),
+ ClientID: os.Getenv("ARM_CLIENT_ID"),
+ ClientSecret: os.Getenv("ARM_CLIENT_SECRET"),
+ TenantID: os.Getenv("ARM_TENANT_ID"),
+ Environment: os.Getenv("ARM_ENVIRONMENT"),
+
+ // Feature Toggles
+ SupportsClientSecretAuth: true,
+ SupportsAzureCliToken: true,
+ }
+
+ config, err := builder.Build()
+ if err != nil {
+ return nil, fmt.Errorf("Error building AzureRM Client: %s", err)
+ }
+
+ env, err := authentication.DetermineEnvironment(config.Environment)
+ if err != nil {
+ return nil, err
+ }
+
+ oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, config.TenantID)
+ if err != nil {
+ return nil, err
+ }
+
+ // OAuthConfigForTenant returns a pointer, which can be nil.
+ if oauthConfig == nil {
+ return nil, fmt.Errorf("Unable to configure OAuthConfig for tenant %s", config.TenantID)
+ }
+
+ // support for HTTP Proxies
+ sender := autorest.DecorateSender(&http.Client{
+ Transport: &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ },
+ })
+
+ storageAuth, err := config.GetAuthorizationToken(sender, oauthConfig, "https://storage.azure.com/")
+ if err != nil {
+ return nil, err
+ }
+
+ containersClient := containers.New()
+ containersClient.Client.Authorizer = storageAuth
+
+ result := &Client{
+ ContainersClient: containersClient,
+ }
+
+ return result, nil
+}
diff --git a/example/file-auth/README.md b/example/file-auth/README.md
new file mode 100644
index 0000000..98f93bc
--- /dev/null
+++ b/example/file-auth/README.md
@@ -0,0 +1,15 @@
+## Example: using a Shared Key
+
+This example provisions a Storage Container using a Shared Key for authentication.
+
+To run this example you need the following Environment Variables set:
+
+* `ARM_ENVIRONMENT` - The Azure Environment (`public`, `germany` etc)
+
+You also need to update `main.go` to set the variable `storageAccountName` and `storageAccountKey` to an existing Storage Account (since we don't provision one for you).
+
+Assuming you've got Go installed - you can then run this using:
+
+```bash
+$ go run main.go
+```
\ No newline at end of file
diff --git a/example/file-auth/main.go b/example/file-auth/main.go
new file mode 100644
index 0000000..7f55be1
--- /dev/null
+++ b/example/file-auth/main.go
@@ -0,0 +1,68 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "os"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/hashicorp/go-azure-helpers/authentication"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers"
+)
+
+func main() {
+ log.Printf("[DEBUG] Started..")
+
+ // NOTE: fill this in
+ storageAccountName := "example"
+ storageAccountKey := "example"
+
+ log.Printf("[DEBUG] Building Client..")
+ client, err := buildClient(storageAccountName, storageAccountKey)
+ if err != nil {
+ panic(fmt.Errorf("Error building client: %s", err))
+ }
+
+ ctx := context.TODO()
+ containerName := "armauth"
+ input := containers.CreateInput{
+ AccessLevel: containers.Private,
+ MetaData: map[string]string{
+ "hello": "world",
+ },
+ }
+ log.Printf("[DEBUG] Creating Container..")
+ if _, err := client.ContainersClient.Create(ctx, storageAccountName, containerName, input); err != nil {
+ panic(fmt.Errorf("Error creating container: %s", err))
+ }
+
+ log.Printf("[DEBUG] Retrieving Container..")
+ container, err := client.ContainersClient.GetProperties(ctx, storageAccountName, containerName)
+ if err != nil {
+ panic(fmt.Errorf("Error reading properties for container: %s", err))
+ }
+
+ log.Printf("[DEBUG] MetaData: %+v", container.MetaData)
+}
+
+type Client struct {
+ ContainersClient containers.Client
+}
+
+func buildClient(accountName, accountKey string) (*Client, error) {
+ env, err := authentication.DetermineEnvironment(os.Getenv("ARM_ENVIRONMENT"))
+ if err != nil {
+ return nil, err
+ }
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, accountKey)
+ containersClient := containers.New()
+ containersClient.Client.Authorizer = storageAuth
+
+ result := &Client{
+ ContainersClient: containersClient,
+ }
+
+ return result, nil
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..60bd9c5
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,17 @@
+module github.com/tombuildsstuff/giovanni
+
+go 1.12
+
+require (
+ contrib.go.opencensus.io/exporter/ocagent v0.5.0 // indirect
+ github.com/Azure/azure-sdk-for-go v30.0.0+incompatible
+ github.com/Azure/go-autorest v12.2.0+incompatible // indirect
+ github.com/Azure/go-autorest/autorest v0.3.0
+ github.com/Azure/go-autorest/autorest/adal v0.1.0
+ github.com/Azure/go-autorest/autorest/azure/cli v0.1.0 // indirect
+ github.com/Azure/go-autorest/autorest/to v0.2.0 // indirect
+ github.com/Azure/go-autorest/autorest/validation v0.1.0
+ github.com/hashicorp/go-azure-helpers v0.4.1
+ go.opencensus.io v0.22.0 // indirect
+ golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..5c2ebe2
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,198 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
+contrib.go.opencensus.io/exporter/ocagent v0.5.0 h1:TKXjQSRS0/cCDrP7KvkgU6SmILtF/yV2TOs/02K/WZQ=
+contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0=
+github.com/Azure/azure-sdk-for-go v21.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-sdk-for-go v29.0.0+incompatible h1:CYPU39ULbGjQBo3gXIqiWouK0C4F+Pt2Zx5CqGvqknE=
+github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-sdk-for-go v30.0.0+incompatible h1:6o1Yzl7wTBYg+xw0pY4qnalaPmEQolubEEdepo1/kmI=
+github.com/Azure/azure-sdk-for-go v30.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/go-autorest v10.15.4+incompatible h1:q+DRrRdbCnkY7f2WxQBx58TwCGkEdMAK/hkZ10g0Pzk=
+github.com/Azure/go-autorest v10.15.4+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/Azure/go-autorest v11.7.0+incompatible h1:gzma19dc9ejB75D90E5S+/wXouzpZyA+CV+/MJPSD/k=
+github.com/Azure/go-autorest v11.7.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/Azure/go-autorest v12.2.0+incompatible h1:2Fxszbg492oAJrcvJlgyVaTqnQYRkxmEK6VPCLLVpBI=
+github.com/Azure/go-autorest v12.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/Azure/go-autorest/autorest v0.3.0 h1:yOmXNB2qa2Kx40wMZB19YyafzjCHacXPk8u0neqa+M0=
+github.com/Azure/go-autorest/autorest v0.3.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg=
+github.com/Azure/go-autorest/autorest/adal v0.1.0 h1:RSw/7EAullliqwkZvgIGDYZWQm1PGKXI8c4aY/87yuU=
+github.com/Azure/go-autorest/autorest/adal v0.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E=
+github.com/Azure/go-autorest/autorest/azure/cli v0.1.0 h1:YTtBrcb6mhA+PoSW8WxFDoIIyjp13XqJeX80ssQtri4=
+github.com/Azure/go-autorest/autorest/azure/cli v0.1.0/go.mod h1:Dk8CUAt/b/PzkfeRsWzVG9Yj3ps8mS8ECztu43rdU8U=
+github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM=
+github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
+github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
+github.com/Azure/go-autorest/autorest/to v0.2.0 h1:nQOZzFCudTh+TvquAtCRjM01VEYx85e9qbwt5ncW4L8=
+github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc=
+github.com/Azure/go-autorest/autorest/validation v0.1.0 h1:ISSNzGUh+ZSzizJWOWzs8bwpXIePbGLW4z/AmUFGH5A=
+github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8=
+github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY=
+github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
+github.com/Azure/go-autorest/tracing v0.1.0 h1:TRBxC5Pj/fIuh4Qob0ZpkggbfT8RC0SubHbpV3p4/Vc=
+github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
+github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4=
+github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dimchansky/utfbom v1.0.0 h1:fGC2kkf4qOoKqZ4q7iIh+Vef4ubC1c38UDsEyZynZPc=
+github.com/dimchansky/utfbom v1.0.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
+github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4=
+github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
+github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
+github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
+github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/grpc-ecosystem/grpc-gateway v1.8.5 h1:2+KSC78XiO6Qy0hIjfc1OD9H+hsaJdJlb8Kqsd41CTE=
+github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-azure-helpers v0.4.1 h1:aEWYW4hxAVVmxmq7nPXGK8F44A6HBXQ4m0vB1M3/20g=
+github.com/hashicorp/go-azure-helpers v0.4.1/go.mod h1:lu62V//auUow6k0IykxLK2DCNW8qTmpm8KqhYVWattA=
+github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
+github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 h1:rlLehGeYg6jfoyz/eDqDU1iRXLKfR42nnNh57ytKEWo=
+golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09 h1:KaQtG+aDELoNmXYas3TVkGNYRuq8JQ1aa7LJt8EXVyo=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd h1:r7DufRZuZbWB7j439YfAzP8RPDa9unLkpwQKUYbIMPI=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
+google.golang.org/api v0.4.0 h1:KKgc1aqhV8wDPbDzlDtpvyjZFY3vjz85FP7p4wcQUyI=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb h1:i1Ppqkc3WQXikh8bXiwHqAN5Rv3/qDCcRk0/Otx73BY=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/storage/2017-07-29/README.md b/storage/2017-07-29/README.md
new file mode 100644
index 0000000..6aa8c0b
--- /dev/null
+++ b/storage/2017-07-29/README.md
@@ -0,0 +1,25 @@
+# Storage API Version 2017-07-29
+
+The following API's are supported by this SDK - more information about each SDK can be found within the README in each package.
+
+## Blob Storage
+
+- [Blobs API](blob/blobs)
+- [Containers API](blob/containers)
+
+## File Storage
+
+- [Directories API](file/directories)
+- [Files API](file/files)
+- [Shares API](file/shares)
+
+## Queue Storage
+
+- [Queues API](queue/queues)
+- [Messages API](queue/messages)
+
+## Table Storage
+
+- [Entities API](table/entities)
+- [Tables API](table/tables)
+
diff --git a/storage/2017-07-29/blob/blobs/README.md b/storage/2017-07-29/blob/blobs/README.md
new file mode 100644
index 0000000..b15f42c
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/README.md
@@ -0,0 +1,45 @@
+## Blob Storage Blobs SDK for API version 2017-07-29
+
+This package allows you to interact with the Blobs Blob Storage API
+
+### Supported Authorizers
+
+* SharedKeyLite (Blob, File & Queue)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/blob/blobs"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ containerName := "mycontainer"
+ fileName := "example-large-file.iso"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ blobClient := blobs.New()
+ blobClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ copyInput := blobs.CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+ refreshInterval := 5 * time.Second
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, fileName, copyInput, refreshInterval); err != nil {
+ return fmt.Errorf("Error copying: %s", err)
+ }
+
+ return nil
+}
+
+```
\ No newline at end of file
diff --git a/storage/2017-07-29/blob/blobs/append_block.go b/storage/2017-07-29/blob/blobs/append_block.go
new file mode 100644
index 0000000..7fed86a
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/append_block.go
@@ -0,0 +1,170 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type AppendBlockInput struct {
+
+ // A number indicating the byte offset to compare.
+ // Append Block will succeed only if the append position is equal to this number.
+ // If it is not, the request will fail with an AppendPositionConditionNotMet
+ // error (HTTP status code 412 – Precondition Failed)
+ BlobConditionAppendPosition *int64
+
+ // The max length in bytes permitted for the append blob.
+ // If the Append Block operation would cause the blob to exceed that limit or if the blob size
+ // is already greater than the value specified in this header, the request will fail with
+ // an MaxBlobSizeConditionNotMet error (HTTP status code 412 – Precondition Failed).
+ BlobConditionMaxSize *int64
+
+ // The Bytes which should be appended to the end of this Append Blob.
+ Content []byte
+
+ // An MD5 hash of the block content.
+ // This hash is used to verify the integrity of the block during transport.
+ // When this header is specified, the storage service compares the hash of the content
+ // that has arrived with this header value.
+ //
+ // Note that this MD5 hash is not stored with the blob.
+ // If the two hashes do not match, the operation will fail with error code 400 (Bad Request).
+ ContentMD5 *string
+
+ // Required if the blob has an active lease.
+ // To perform this operation on a blob with an active lease, specify the valid lease ID for this header.
+ LeaseID *string
+}
+
+type AppendBlockResult struct {
+ autorest.Response
+
+ BlobAppendOffset string
+ BlobCommittedBlockCount int64
+ ContentMD5 string
+ ETag string
+ LastModified string
+}
+
+// AppendBlock commits a new block of data to the end of an existing append blob.
+func (client Client) AppendBlock(ctx context.Context, accountName, containerName, blobName string, input AppendBlockInput) (result AppendBlockResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "AppendBlock", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "AppendBlock", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "AppendBlock", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "AppendBlock", "`blobName` cannot be an empty string.")
+ }
+ if len(input.Content) > (4 * 1024 * 1024) {
+ return result, validation.NewError("files.Client", "PutByteRange", "`input.Content` must be at most 4MB.")
+ }
+
+ req, err := client.AppendBlockPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AppendBlock", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.AppendBlockSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AppendBlock", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.AppendBlockResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AppendBlock", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// AppendBlockPreparer prepares the AppendBlock request.
+func (client Client) AppendBlockPreparer(ctx context.Context, accountName, containerName, blobName string, input AppendBlockInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "appendblock"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.BlobConditionAppendPosition != nil {
+ headers["x-ms-blob-condition-appendpos"] = *input.BlobConditionAppendPosition
+ }
+ if input.BlobConditionMaxSize != nil {
+ headers["x-ms-blob-condition-maxsize"] = *input.BlobConditionMaxSize
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithBytes(&input.Content))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// AppendBlockSender sends the AppendBlock request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) AppendBlockSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// AppendBlockResponder handles the response to the AppendBlock request. The method always
+// closes the http.Response Body.
+func (client Client) AppendBlockResponder(resp *http.Response) (result AppendBlockResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.BlobAppendOffset = resp.Header.Get("x-ms-blob-append-offset")
+ result.ContentMD5 = resp.Header.Get("ETag")
+ result.ETag = resp.Header.Get("ETag")
+ result.LastModified = resp.Header.Get("Last-Modified")
+
+ if v := resp.Header.Get("x-ms-blob-committed-block-count"); v != "" {
+ i, innerErr := strconv.Atoi(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as an integer: %s", v, innerErr)
+ return
+ }
+
+ result.BlobCommittedBlockCount = int64(i)
+ }
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/blob_append_test.go b/storage/2017-07-29/blob/blobs/blob_append_test.go
new file mode 100644
index 0000000..78b2bcd
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/blob_append_test.go
@@ -0,0 +1,155 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/blob/containers"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestAppendBlobLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "append-blob.txt"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithAuthorizer(containersClient.Client, storageAuth)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Putting Append Blob..")
+ if _, err := blobClient.PutAppendBlob(ctx, accountName, containerName, fileName, PutAppendBlobInput{}); err != nil {
+ t.Fatalf("Error putting append blob: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Properties..")
+ props, err := blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error retrieving properties: %s", err)
+ }
+ if props.ContentLength != 0 {
+ t.Fatalf("Expected Content-Length to be 0 but it was %d", props.ContentLength)
+ }
+
+ t.Logf("[DEBUG] Appending First Block..")
+ appendInput := AppendBlockInput{
+ Content: []byte{
+ 12,
+ 48,
+ 93,
+ 76,
+ 29,
+ 10,
+ },
+ }
+ if _, err := blobClient.AppendBlock(ctx, accountName, containerName, fileName, appendInput); err != nil {
+ t.Fatalf("Error appending first block: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-Retrieving Properties..")
+ props, err = blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error retrieving properties: %s", err)
+ }
+ if props.ContentLength != 6 {
+ t.Fatalf("Expected Content-Length to be 6 but it was %d", props.ContentLength)
+ }
+
+ t.Logf("[DEBUG] Appending Second Block..")
+ appendInput = AppendBlockInput{
+ Content: []byte{
+ 92,
+ 62,
+ 64,
+ 47,
+ 83,
+ 77,
+ },
+ }
+ if _, err := blobClient.AppendBlock(ctx, accountName, containerName, fileName, appendInput); err != nil {
+ t.Fatalf("Error appending Second block: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-Retrieving Properties..")
+ props, err = blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error retrieving properties: %s", err)
+ }
+ if props.ContentLength != 12 {
+ t.Fatalf("Expected Content-Length to be 12 but it was %d", props.ContentLength)
+ }
+
+ t.Logf("[DEBUG] Acquiring Lease..")
+ leaseDetails, err := blobClient.AcquireLease(ctx, accountName, containerName, fileName, AcquireLeaseInput{
+ LeaseDuration: -1,
+ })
+ if err != nil {
+ t.Fatalf("Error acquiring Lease: %s", err)
+ }
+ t.Logf("[DEBUG] Lease ID is %q", leaseDetails.LeaseID)
+
+ t.Logf("[DEBUG] Appending Third Block..")
+ appendInput = AppendBlockInput{
+ Content: []byte{
+ 64,
+ 35,
+ 28,
+ 93,
+ 11,
+ 23,
+ },
+ LeaseID: &leaseDetails.LeaseID,
+ }
+ if _, err := blobClient.AppendBlock(ctx, accountName, containerName, fileName, appendInput); err != nil {
+ t.Fatalf("Error appending Third block: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-Retrieving Properties..")
+ props, err = blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{
+ LeaseID: &leaseDetails.LeaseID,
+ })
+ if err != nil {
+ t.Fatalf("Error retrieving properties: %s", err)
+ }
+ if props.ContentLength != 18 {
+ t.Fatalf("Expected Content-Length to be 18 but it was %d", props.ContentLength)
+ }
+
+ t.Logf("[DEBUG] Breaking Lease..")
+ breakLeaseInput := BreakLeaseInput{
+ LeaseID: leaseDetails.LeaseID,
+ }
+ if _, err := blobClient.BreakLease(ctx, accountName, containerName, fileName, breakLeaseInput); err != nil {
+ t.Fatalf("Error breaking lease: %s", err)
+ }
+
+ t.Logf("[DEBUG] Deleting Lease..")
+ if _, err := blobClient.Delete(ctx, accountName, containerName, fileName, DeleteInput{}); err != nil {
+ t.Fatalf("Error deleting: %s", err)
+ }
+}
diff --git a/storage/2017-07-29/blob/blobs/blob_page_test.go b/storage/2017-07-29/blob/blobs/blob_page_test.go
new file mode 100644
index 0000000..6f6b87e
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/blob_page_test.go
@@ -0,0 +1,89 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/blob/containers"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestPageBlobLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "append-blob.txt"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.StorageV2)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithAuthorizer(containersClient.Client, storageAuth)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Putting Page Blob..")
+ fileSize := int64(10240000)
+ if _, err := blobClient.PutPageBlob(ctx, accountName, containerName, fileName, PutPageBlobInput{
+ BlobContentLengthBytes: fileSize,
+ }); err != nil {
+ t.Fatalf("Error putting page blob: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Properties..")
+ props, err := blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error retrieving properties: %s", err)
+ }
+ if props.ContentLength != fileSize {
+ t.Fatalf("Expected Content-Length to be %d but it was %d", fileSize, props.ContentLength)
+ }
+
+ for iteration := 1; iteration <= 3; iteration++ {
+ t.Logf("[DEBUG] Putting Page %d of 3..", iteration)
+ byteArray := func() []byte {
+ o := make([]byte, 0)
+
+ for i := 0; i < 512; i++ {
+ o = append(o, byte(i))
+ }
+
+ return o
+ }()
+ startByte := int64(512 * iteration)
+ endByte := int64(startByte + 511)
+ putPageInput := PutPageUpdateInput{
+ StartByte: startByte,
+ EndByte: endByte,
+ Content: byteArray,
+ }
+ if _, err := blobClient.PutPageUpdate(ctx, accountName, containerName, fileName, putPageInput); err != nil {
+ t.Fatalf("Error putting page: %s", err)
+ }
+ }
+
+ t.Logf("[DEBUG] Deleting..")
+ if _, err := blobClient.Delete(ctx, accountName, containerName, fileName, DeleteInput{}); err != nil {
+ t.Fatalf("Error deleting: %s", err)
+ }
+}
diff --git a/storage/2017-07-29/blob/blobs/client.go b/storage/2017-07-29/blob/blobs/client.go
new file mode 100644
index 0000000..db20391
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/client.go
@@ -0,0 +1,25 @@
+package blobs
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for Blob Storage Blobs.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithBaseURI creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2017-07-29/blob/blobs/copy.go b/storage/2017-07-29/blob/blobs/copy.go
new file mode 100644
index 0000000..febaab5
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/copy.go
@@ -0,0 +1,235 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type CopyInput struct {
+ // Specifies the name of the source blob or file.
+ // Beginning with version 2012-02-12, this value may be a URL of up to 2 KB in length that specifies a blob.
+ // The value should be URL-encoded as it would appear in a request URI.
+ // A source blob in the same storage account can be authenticated via Shared Key.
+ // However, if the source is a blob in another account,
+ // the source blob must either be public or must be authenticated via a shared access signature.
+ // If the source blob is public, no authentication is required to perform the copy operation.
+ //
+ // Beginning with version 2015-02-21, the source object may be a file in the Azure File service.
+ // If the source object is a file that is to be copied to a blob, then the source file must be authenticated
+ // using a shared access signature, whether it resides in the same account or in a different account.
+ //
+ // Only storage accounts created on or after June 7th, 2012 allow the Copy Blob operation to
+ // copy from another storage account.
+ CopySource string
+
+ // The ID of the Lease
+ // Required if the destination blob has an active lease.
+ // The lease ID specified for this header must match the lease ID of the destination blob.
+ // If the request does not include the lease ID or it is not valid,
+ // the operation fails with status code 412 (Precondition Failed).
+ //
+ // If this header is specified and the destination blob does not currently have an active lease,
+ // the operation will also fail with status code 412 (Precondition Failed).
+ LeaseID *string
+
+ // The ID of the Lease on the Source Blob
+ // Specify to perform the Copy Blob operation only if the lease ID matches the active lease ID of the source blob.
+ SourceLeaseID *string
+
+ // For page blobs on a premium account only. Specifies the tier to be set on the target blob
+ AccessTier *AccessTier
+
+ // A user-defined name-value pair associated with the blob.
+ // If no name-value pairs are specified, the operation will copy the metadata from the source blob or
+ // file to the destination blob.
+ // If one or more name-value pairs are specified, the destination blob is created with the specified metadata,
+ // and metadata is not copied from the source blob or file.
+ MetaData map[string]string
+
+ // An ETag value.
+ // Specify an ETag value for this conditional header to copy the blob only if the specified
+ // ETag value matches the ETag value for an existing destination blob.
+ // If the ETag for the destination blob does not match the ETag specified for If-Match,
+ // the Blob service returns status code 412 (Precondition Failed).
+ IfMatch *string
+
+ // An ETag value, or the wildcard character (*).
+ // Specify an ETag value for this conditional header to copy the blob only if the specified
+ // ETag value does not match the ETag value for the destination blob.
+ // Specify the wildcard character (*) to perform the operation only if the destination blob does not exist.
+ // If the specified condition isn't met, the Blob service returns status code 412 (Precondition Failed).
+ IfNoneMatch *string
+
+ // A DateTime value.
+ // Specify this conditional header to copy the blob only if the destination blob
+ // has been modified since the specified date/time.
+ // If the destination blob has not been modified, the Blob service returns status code 412 (Precondition Failed).
+ IfModifiedSince *string
+
+ // A DateTime value.
+ // Specify this conditional header to copy the blob only if the destination blob
+ // has not been modified since the specified date/time.
+ // If the destination blob has been modified, the Blob service returns status code 412 (Precondition Failed).
+ IfUnmodifiedSince *string
+
+ // An ETag value.
+ // Specify this conditional header to copy the source blob only if its ETag matches the value specified.
+ // If the ETag values do not match, the Blob service returns status code 412 (Precondition Failed).
+ // This cannot be specified if the source is an Azure File.
+ SourceIfMatch *string
+
+ // An ETag value.
+ // Specify this conditional header to copy the blob only if its ETag does not match the value specified.
+ // If the values are identical, the Blob service returns status code 412 (Precondition Failed).
+ // This cannot be specified if the source is an Azure File.
+ SourceIfNoneMatch *string
+
+ // A DateTime value.
+ // Specify this conditional header to copy the blob only if the source blob has been modified
+ // since the specified date/time.
+ // If the source blob has not been modified, the Blob service returns status code 412 (Precondition Failed).
+ // This cannot be specified if the source is an Azure File.
+ SourceIfModifiedSince *string
+
+ // A DateTime value.
+ // Specify this conditional header to copy the blob only if the source blob has not been modified
+ // since the specified date/time.
+ // If the source blob has been modified, the Blob service returns status code 412 (Precondition Failed).
+ // This header cannot be specified if the source is an Azure File.
+ SourceIfUnmodifiedSince *string
+}
+
+type CopyResult struct {
+ autorest.Response
+
+ CopyID string
+ CopyStatus string
+}
+
+// Copy copies a blob to a destination within the storage account asynchronously.
+func (client Client) Copy(ctx context.Context, accountName, containerName, blobName string, input CopyInput) (result CopyResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "Copy", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "Copy", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "Copy", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "Copy", "`blobName` cannot be an empty string.")
+ }
+ if input.CopySource == "" {
+ return result, validation.NewError("blobs.Client", "Copy", "`input.CopySource` cannot be an empty string.")
+ }
+
+ req, err := client.CopyPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Copy", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CopySender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Copy", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CopyResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Copy", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CopyPreparer prepares the Copy request.
+func (client Client) CopyPreparer(ctx context.Context, accountName, containerName, blobName string, input CopyInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-copy-source": autorest.Encode("header", input.CopySource),
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+ if input.SourceLeaseID != nil {
+ headers["x-ms-source-lease-id"] = *input.SourceLeaseID
+ }
+ if input.AccessTier != nil {
+ headers["x-ms-access-tier"] = string(*input.AccessTier)
+ }
+
+ if input.IfMatch != nil {
+ headers["If-Match"] = *input.IfMatch
+ }
+ if input.IfNoneMatch != nil {
+ headers["If-None-Match"] = *input.IfNoneMatch
+ }
+ if input.IfUnmodifiedSince != nil {
+ headers["If-Unmodified-Since"] = *input.IfUnmodifiedSince
+ }
+ if input.IfModifiedSince != nil {
+ headers["If-Modified-Since"] = *input.IfModifiedSince
+ }
+
+ if input.SourceIfMatch != nil {
+ headers["x-ms-source-if-match"] = *input.SourceIfMatch
+ }
+ if input.SourceIfNoneMatch != nil {
+ headers["x-ms-source-if-none-match"] = *input.SourceIfNoneMatch
+ }
+ if input.SourceIfModifiedSince != nil {
+ headers["x-ms-source-if-modified-since"] = *input.SourceIfModifiedSince
+ }
+ if input.SourceIfUnmodifiedSince != nil {
+ headers["x-ms-source-if-unmodified-since"] = *input.SourceIfUnmodifiedSince
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CopySender sends the Copy request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CopySender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CopyResponder handles the response to the Copy request. The method always
+// closes the http.Response Body.
+func (client Client) CopyResponder(resp *http.Response) (result CopyResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.CopyID = resp.Header.Get("x-ms-copy-id")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/copy_abort.go b/storage/2017-07-29/blob/blobs/copy_abort.go
new file mode 100644
index 0000000..a992ff1
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/copy_abort.go
@@ -0,0 +1,110 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type AbortCopyInput struct {
+ // The Copy ID which should be aborted
+ CopyID string
+
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+}
+
+// AbortCopy aborts a pending Copy Blob operation, and leaves a destination blob with zero length and full metadata.
+func (client Client) AbortCopy(ctx context.Context, accountName, containerName, blobName string, input AbortCopyInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "AbortCopy", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "AbortCopy", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "AbortCopy", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "AbortCopy", "`blobName` cannot be an empty string.")
+ }
+ if input.CopyID == "" {
+ return result, validation.NewError("blobs.Client", "AbortCopy", "`input.CopyID` cannot be an empty string.")
+ }
+
+ req, err := client.AbortCopyPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AbortCopy", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.AbortCopySender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AbortCopy", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.AbortCopyResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AbortCopy", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// AbortCopyPreparer prepares the AbortCopy request.
+func (client Client) AbortCopyPreparer(ctx context.Context, accountName, containerName, blobName string, input AbortCopyInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "copy"),
+ "copyid": autorest.Encode("query", input.CopyID),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-copy-action": "abort",
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// AbortCopySender sends the AbortCopy request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) AbortCopySender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// AbortCopyResponder handles the response to the AbortCopy request. The method always
+// closes the http.Response Body.
+func (client Client) AbortCopyResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/copy_and_wait.go b/storage/2017-07-29/blob/blobs/copy_and_wait.go
new file mode 100644
index 0000000..a1e7fa4
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/copy_and_wait.go
@@ -0,0 +1,41 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "time"
+)
+
+// CopyAndWait copies a blob to a destination within the storage account and waits for it to finish copying.
+func (client Client) CopyAndWait(ctx context.Context, accountName, containerName, blobName string, input CopyInput, pollingInterval time.Duration) error {
+ if _, err := client.Copy(ctx, accountName, containerName, blobName, input); err != nil {
+ return fmt.Errorf("Error copying: %s", err)
+ }
+
+ for true {
+ getInput := GetPropertiesInput{
+ LeaseID: input.LeaseID,
+ }
+ getResult, err := client.GetProperties(ctx, accountName, containerName, blobName, getInput)
+ if err != nil {
+ return fmt.Errorf("")
+ }
+
+ switch getResult.CopyStatus {
+ case Aborted:
+ return fmt.Errorf("Copy was aborted: %s", getResult.CopyStatusDescription)
+
+ case Failed:
+ return fmt.Errorf("Copy failed: %s", getResult.CopyStatusDescription)
+
+ case Success:
+ return nil
+
+ case Pending:
+ time.Sleep(pollingInterval)
+ continue
+ }
+ }
+
+ return fmt.Errorf("Unexpected error waiting for the copy to complete")
+}
diff --git a/storage/2017-07-29/blob/blobs/copy_test.go b/storage/2017-07-29/blob/blobs/copy_test.go
new file mode 100644
index 0000000..76bc62f
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/copy_test.go
@@ -0,0 +1,148 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/blob/containers"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestCopyFromExistingFile(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "ubuntu.iso"
+ copiedFileName := "copied.iso"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithAuthorizer(containersClient.Client, storageAuth)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Copying file to Blob Storage..")
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+
+ refreshInterval := 5 * time.Second
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, fileName, copyInput, refreshInterval); err != nil {
+ t.Fatalf("Error copying: %s", err)
+ }
+
+ t.Logf("[DEBUG] Duplicating that file..")
+ copiedInput := CopyInput{
+ CopySource: fmt.Sprintf("%s/%s/%s", endpoints.GetBlobEndpoint(blobClient.BaseURI, accountName), containerName, fileName),
+ }
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, copiedFileName, copiedInput, refreshInterval); err != nil {
+ t.Fatalf("Error duplicating file: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Properties for the Original File..")
+ props, err := blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error getting properties for the original file: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Properties for the Copied File..")
+ copiedProps, err := blobClient.GetProperties(ctx, accountName, containerName, copiedFileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error getting properties for the copied file: %s", err)
+ }
+
+ if props.ContentLength != copiedProps.ContentLength {
+ t.Fatalf("Expected the content length to be %d but it was %d", props.ContentLength, copiedProps.ContentLength)
+ }
+
+ t.Logf("[DEBUG] Deleting copied file..")
+ if _, err := blobClient.Delete(ctx, accountName, containerName, copiedFileName, DeleteInput{}); err != nil {
+ t.Fatalf("Error deleting file: %s", err)
+ }
+
+ t.Logf("[DEBUG] Deleting original file..")
+ if _, err := blobClient.Delete(ctx, accountName, containerName, fileName, DeleteInput{}); err != nil {
+ t.Fatalf("Error deleting file: %s", err)
+ }
+}
+
+func TestCopyFromURL(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "ubuntu.iso"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithAuthorizer(containersClient.Client, storageAuth)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Copying file to Blob Storage..")
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+
+ refreshInterval := 5 * time.Second
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, fileName, copyInput, refreshInterval); err != nil {
+ t.Fatalf("Error copying: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Properties..")
+ props, err := blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error getting properties: %s", err)
+ }
+
+ if props.ContentLength == 0 {
+ t.Fatalf("Expected the file to be there but looks like it isn't: %d", props.ContentLength)
+ }
+
+ t.Logf("[DEBUG] Deleting file..")
+ if _, err := blobClient.Delete(ctx, accountName, containerName, fileName, DeleteInput{}); err != nil {
+ t.Fatalf("Error deleting file: %s", err)
+ }
+}
diff --git a/storage/2017-07-29/blob/blobs/delete.go b/storage/2017-07-29/blob/blobs/delete.go
new file mode 100644
index 0000000..c1c642d
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/delete.go
@@ -0,0 +1,105 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type DeleteInput struct {
+ // Should any Snapshots for this Blob also be deleted?
+ // If the Blob has Snapshots and this is set to False a 409 Conflict will be returned
+ DeleteSnapshots bool
+
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+}
+
+// Delete marks the specified blob or snapshot for deletion. The blob is later deleted during garbage collection.
+func (client Client) Delete(ctx context.Context, accountName, containerName, blobName string, input DeleteInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "Delete", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "Delete", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "Delete", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, containerName, blobName string, input DeleteInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ if input.DeleteSnapshots {
+ headers["x-ms-delete-snapshots"] = "include"
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/delete_snapshot.go b/storage/2017-07-29/blob/blobs/delete_snapshot.go
new file mode 100644
index 0000000..18c3d4c
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/delete_snapshot.go
@@ -0,0 +1,108 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type DeleteSnapshotInput struct {
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+
+ // The DateTime of the Snapshot which should be marked for Deletion
+ SnapshotDateTime string
+}
+
+// DeleteSnapshot marks a single Snapshot of a Blob for Deletion based on it's DateTime, which will be deleted during the next Garbage Collection cycle.
+func (client Client) DeleteSnapshot(ctx context.Context, accountName, containerName, blobName string, input DeleteSnapshotInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshot", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshot", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshot", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshot", "`blobName` cannot be an empty string.")
+ }
+ if input.SnapshotDateTime == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshot", "`input.SnapshotDateTime` cannot be an empty string.")
+ }
+
+ req, err := client.DeleteSnapshotPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "DeleteSnapshot", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSnapshotSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "DeleteSnapshot", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteSnapshotResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "DeleteSnapshot", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeleteSnapshotPreparer prepares the DeleteSnapshot request.
+func (client Client) DeleteSnapshotPreparer(ctx context.Context, accountName, containerName, blobName string, input DeleteSnapshotInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "snapshot": autorest.Encode("query", input.SnapshotDateTime),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSnapshotSender sends the DeleteSnapshot request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSnapshotSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteSnapshotResponder handles the response to the DeleteSnapshot request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteSnapshotResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/delete_snapshots.go b/storage/2017-07-29/blob/blobs/delete_snapshots.go
new file mode 100644
index 0000000..e7e2b66
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/delete_snapshots.go
@@ -0,0 +1,99 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type DeleteSnapshotsInput struct {
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+}
+
+// DeleteSnapshots marks all Snapshots of a Blob for Deletion, which will be deleted during the next Garbage Collection Cycle.
+func (client Client) DeleteSnapshots(ctx context.Context, accountName, containerName, blobName string, input DeleteSnapshotsInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshots", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshots", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshots", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshots", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.DeleteSnapshotsPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "DeleteSnapshots", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSnapshotsSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "DeleteSnapshots", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteSnapshotsResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "DeleteSnapshots", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeleteSnapshotsPreparer prepares the DeleteSnapshots request.
+func (client Client) DeleteSnapshotsPreparer(ctx context.Context, accountName, containerName, blobName string, input DeleteSnapshotsInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ // only delete the snapshots but leave the blob as-is
+ "x-ms-delete-snapshots": "only",
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSnapshotsSender sends the DeleteSnapshots request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSnapshotsSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteSnapshotsResponder handles the response to the DeleteSnapshots request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteSnapshotsResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/get.go b/storage/2017-07-29/blob/blobs/get.go
new file mode 100644
index 0000000..fa88081
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/get.go
@@ -0,0 +1,116 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetInput struct {
+ LeaseID *string
+ StartByte *int64
+ EndByte *int64
+}
+
+type GetResult struct {
+ autorest.Response
+
+ Contents []byte
+}
+
+// Get reads or downloads a blob from the system, including its metadata and properties.
+func (client Client) Get(ctx context.Context, accountName, containerName, blobName string, input GetInput) (result GetResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "Get", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "Get", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "Get", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "Get", "`blobName` cannot be an empty string.")
+ }
+ if input.LeaseID != nil && *input.LeaseID == "" {
+ return result, validation.NewError("blobs.Client", "Get", "`input.LeaseID` should either be specified or nil, not an empty string.")
+ }
+ if (input.StartByte != nil && input.EndByte == nil) || input.StartByte == nil && input.EndByte != nil {
+ return result, validation.NewError("blobs.Client", "Get", "`input.StartByte` and `input.EndByte` must both be specified, or both be nil.")
+ }
+
+ req, err := client.GetPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Get", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Get", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Get", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPreparer prepares the Get request.
+func (client Client) GetPreparer(ctx context.Context, accountName, containerName, blobName string, input GetInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.StartByte != nil && input.EndByte != nil {
+ headers["x-ms-range"] = fmt.Sprintf("bytes=%d-%d", *input.StartByte, *input.EndByte)
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetSender sends the Get request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetResponder handles the response to the Get request. The method always
+// closes the http.Response Body.
+func (client Client) GetResponder(resp *http.Response) (result GetResult, err error) {
+ if resp != nil {
+ result.Contents = make([]byte, resp.ContentLength)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusPartialContent),
+ autorest.ByUnmarshallingBytes(&result.Contents),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/get_block_list.go b/storage/2017-07-29/blob/blobs/get_block_list.go
new file mode 100644
index 0000000..9f8120c
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/get_block_list.go
@@ -0,0 +1,140 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetBlockListInput struct {
+ BlockListType BlockListType
+ LeaseID *string
+}
+
+type GetBlockListResult struct {
+ autorest.Response
+
+ // The size of the blob in bytes
+ ContentLength *int64
+
+ // The Content Type of the blob
+ ContentType string
+
+ // The ETag associated with this blob
+ ETag string
+
+ // A list of blocks which have been committed
+ CommittedBlocks CommittedBlocks `xml:"CommittedBlocks,omitempty"`
+
+ // A list of blocks which have not yet been committed
+ UncommittedBlocks UncommittedBlocks `xml:"UncommittedBlocks,omitempty"`
+}
+
+// GetBlockList retrieves the list of blocks that have been uploaded as part of a block blob.
+func (client Client) GetBlockList(ctx context.Context, accountName, containerName, blobName string, input GetBlockListInput) (result GetBlockListResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "GetBlockList", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "GetBlockList", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "GetBlockList", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "GetBlockList", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.GetBlockListPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetBlockList", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetBlockListSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetBlockList", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetBlockListResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetBlockList", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetBlockListPreparer prepares the GetBlockList request.
+func (client Client) GetBlockListPreparer(ctx context.Context, accountName, containerName, blobName string, input GetBlockListInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "blocklisttype": autorest.Encode("query", string(input.BlockListType)),
+ "comp": autorest.Encode("query", "blocklist"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetBlockListSender sends the GetBlockList request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetBlockListSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetBlockListResponder handles the response to the GetBlockList request. The method always
+// closes the http.Response Body.
+func (client Client) GetBlockListResponder(resp *http.Response) (result GetBlockListResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.ContentType = resp.Header.Get("Content-Type")
+ result.ETag = resp.Header.Get("ETag")
+
+ if v := resp.Header.Get("x-ms-blob-content-length"); v != "" {
+ i, innerErr := strconv.Atoi(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as an integer: %s", v, innerErr)
+ return
+ }
+
+ i64 := int64(i)
+ result.ContentLength = &i64
+ }
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/get_page_ranges.go b/storage/2017-07-29/blob/blobs/get_page_ranges.go
new file mode 100644
index 0000000..37abf63
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/get_page_ranges.go
@@ -0,0 +1,152 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetPageRangesInput struct {
+ LeaseID *string
+
+ StartByte *int64
+ EndByte *int64
+}
+
+type GetPageRangesResult struct {
+ autorest.Response
+
+ // The size of the blob in bytes
+ ContentLength *int64
+
+ // The Content Type of the blob
+ ContentType string
+
+ // The ETag associated with this blob
+ ETag string
+
+ PageRanges []PageRange `xml:"PageRange"`
+}
+
+type PageRange struct {
+ // The start byte offset for this range, inclusive
+ Start int64 `xml:"Start"`
+
+ // The end byte offset for this range, inclusive
+ End int64 `xml:"End"`
+}
+
+// GetPageRanges returns the list of valid page ranges for a page blob or snapshot of a page blob.
+func (client Client) GetPageRanges(ctx context.Context, accountName, containerName, blobName string, input GetPageRangesInput) (result GetPageRangesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "GetPageRanges", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "GetPageRanges", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "GetPageRanges", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "GetPageRanges", "`blobName` cannot be an empty string.")
+ }
+ if (input.StartByte != nil && input.EndByte == nil) || input.StartByte == nil && input.EndByte != nil {
+ return result, validation.NewError("blobs.Client", "GetPageRanges", "`input.StartByte` and `input.EndByte` must both be specified, or both be nil.")
+ }
+
+ req, err := client.GetPageRangesPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetPageRanges", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetPageRangesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetPageRanges", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetPageRangesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetPageRanges", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPageRangesPreparer prepares the GetPageRanges request.
+func (client Client) GetPageRangesPreparer(ctx context.Context, accountName, containerName, blobName string, input GetPageRangesInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "pagelist"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ if input.StartByte != nil && input.EndByte != nil {
+ headers["x-ms-range"] = fmt.Sprintf("bytes=%d-%d", *input.StartByte, *input.EndByte)
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetPageRangesSender sends the GetPageRanges request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetPageRangesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetPageRangesResponder handles the response to the GetPageRanges request. The method always
+// closes the http.Response Body.
+func (client Client) GetPageRangesResponder(resp *http.Response) (result GetPageRangesResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.ContentType = resp.Header.Get("Content-Type")
+ result.ETag = resp.Header.Get("ETag")
+
+ if v := resp.Header.Get("x-ms-blob-content-length"); v != "" {
+ i, innerErr := strconv.Atoi(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as an integer: %s", v, innerErr)
+ return
+ }
+
+ i64 := int64(i)
+ result.ContentLength = &i64
+ }
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/incremental_copy_blob.go b/storage/2017-07-29/blob/blobs/incremental_copy_blob.go
new file mode 100644
index 0000000..7fb7e6b
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/incremental_copy_blob.go
@@ -0,0 +1,120 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type IncrementalCopyBlobInput struct {
+ CopySource string
+ IfModifiedSince *string
+ IfUnmodifiedSince *string
+ IfMatch *string
+ IfNoneMatch *string
+}
+
+// IncrementalCopyBlob copies a snapshot of the source page blob to a destination page blob.
+// The snapshot is copied such that only the differential changes between the previously copied
+// snapshot are transferred to the destination.
+// The copied snapshots are complete copies of the original snapshot and can be read or copied from as usual.
+func (client Client) IncrementalCopyBlob(ctx context.Context, accountName, containerName, blobName string, input IncrementalCopyBlobInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "IncrementalCopyBlob", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "IncrementalCopyBlob", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "IncrementalCopyBlob", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "IncrementalCopyBlob", "`blobName` cannot be an empty string.")
+ }
+ if input.CopySource == "" {
+ return result, validation.NewError("blobs.Client", "IncrementalCopyBlob", "`input.CopySource` cannot be an empty string.")
+ }
+
+ req, err := client.IncrementalCopyBlobPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "IncrementalCopyBlob", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.IncrementalCopyBlobSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "IncrementalCopyBlob", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.IncrementalCopyBlobResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "IncrementalCopyBlob", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// IncrementalCopyBlobPreparer prepares the IncrementalCopyBlob request.
+func (client Client) IncrementalCopyBlobPreparer(ctx context.Context, accountName, containerName, blobName string, input IncrementalCopyBlobInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "incrementalcopy"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-copy-source": input.CopySource,
+ }
+
+ if input.IfModifiedSince != nil {
+ headers["If-Modified-Since"] = *input.IfModifiedSince
+ }
+ if input.IfUnmodifiedSince != nil {
+ headers["If-Unmodified-Since"] = *input.IfUnmodifiedSince
+ }
+ if input.IfMatch != nil {
+ headers["If-Match"] = *input.IfMatch
+ }
+ if input.IfNoneMatch != nil {
+ headers["If-None-Match"] = *input.IfNoneMatch
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// IncrementalCopyBlobSender sends the IncrementalCopyBlob request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) IncrementalCopyBlobSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// IncrementalCopyBlobResponder handles the response to the IncrementalCopyBlob request. The method always
+// closes the http.Response Body.
+func (client Client) IncrementalCopyBlobResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/lease_acquire.go b/storage/2017-07-29/blob/blobs/lease_acquire.go
new file mode 100644
index 0000000..432c1f5
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/lease_acquire.go
@@ -0,0 +1,135 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type AcquireLeaseInput struct {
+ // The ID of the existing Lease, if leased
+ LeaseID *string
+
+ // Specifies the duration of the lease, in seconds, or negative one (-1) for a lease that never expires.
+ // A non-infinite lease can be between 15 and 60 seconds
+ LeaseDuration int
+
+ // The Proposed new ID for the Lease
+ ProposedLeaseID *string
+}
+
+type AcquireLeaseResult struct {
+ autorest.Response
+
+ LeaseID string
+}
+
+// AcquireLease establishes and manages a lock on a blob for write and delete operations.
+func (client Client) AcquireLease(ctx context.Context, accountName, containerName, blobName string, input AcquireLeaseInput) (result AcquireLeaseResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`blobName` cannot be an empty string.")
+ }
+ if input.LeaseID != nil && *input.LeaseID == "" {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`input.LeaseID` cannot be an empty string, if specified.")
+ }
+ if input.ProposedLeaseID != nil && *input.ProposedLeaseID == "" {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`input.ProposedLeaseID` cannot be an empty string, if specified.")
+ }
+ // An infinite lease duration is -1 seconds. A non-infinite lease can be between 15 and 60 seconds
+ if input.LeaseDuration != -1 && (input.LeaseDuration <= 15 || input.LeaseDuration >= 60) {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`input.LeaseDuration` must be -1 (infinite), or between 15 and 60 seconds.")
+ }
+
+ req, err := client.AcquireLeasePreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AcquireLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.AcquireLeaseSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AcquireLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.AcquireLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AcquireLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// AcquireLeasePreparer prepares the AcquireLease request.
+func (client Client) AcquireLeasePreparer(ctx context.Context, accountName, containerName, blobName string, input AcquireLeaseInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "acquire",
+ "x-ms-lease-duration": input.LeaseDuration,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ if input.ProposedLeaseID != nil {
+ headers["x-ms-proposed-lease-id"] = input.ProposedLeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// AcquireLeaseSender sends the AcquireLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) AcquireLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// AcquireLeaseResponder handles the response to the AcquireLease request. The method always
+// closes the http.Response Body.
+func (client Client) AcquireLeaseResponder(resp *http.Response) (result AcquireLeaseResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.LeaseID = resp.Header.Get("x-ms-lease-id")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/lease_break.go b/storage/2017-07-29/blob/blobs/lease_break.go
new file mode 100644
index 0000000..d564204
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/lease_break.go
@@ -0,0 +1,124 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type BreakLeaseInput struct {
+ // For a break operation, proposed duration the lease should continue
+ // before it is broken, in seconds, between 0 and 60.
+ // This break period is only used if it is shorter than the time remaining on the lease.
+ // If longer, the time remaining on the lease is used.
+ // A new lease will not be available before the break period has expired,
+ // but the lease may be held for longer than the break period.
+ // If this header does not appear with a break operation, a fixed-duration lease breaks
+ // after the remaining lease period elapses, and an infinite lease breaks immediately.
+ BreakPeriod *int
+
+ LeaseID string
+}
+
+type BreakLeaseResponse struct {
+ autorest.Response
+
+ // Approximate time remaining in the lease period, in seconds.
+ // If the break is immediate, 0 is returned.
+ LeaseTime int
+}
+
+// BreakLease breaks an existing lock on a blob using the LeaseID.
+func (client Client) BreakLease(ctx context.Context, accountName, containerName, blobName string, input BreakLeaseInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "BreakLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "BreakLease", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "BreakLease", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "BreakLease", "`blobName` cannot be an empty string.")
+ }
+ if input.LeaseID == "" {
+ return result, validation.NewError("blobs.Client", "BreakLease", "`input.LeaseID` cannot be an empty string.")
+ }
+
+ req, err := client.BreakLeasePreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "BreakLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.BreakLeaseSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "BreakLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.BreakLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "BreakLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// BreakLeasePreparer prepares the BreakLease request.
+func (client Client) BreakLeasePreparer(ctx context.Context, accountName, containerName, blobName string, input BreakLeaseInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "break",
+ "x-ms-lease-id": input.LeaseID,
+ }
+
+ if input.BreakPeriod != nil {
+ headers["x-ms-lease-break-period"] = *input.BreakPeriod
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// BreakLeaseSender sends the BreakLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) BreakLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// BreakLeaseResponder handles the response to the BreakLease request. The method always
+// closes the http.Response Body.
+func (client Client) BreakLeaseResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/lease_change.go b/storage/2017-07-29/blob/blobs/lease_change.go
new file mode 100644
index 0000000..c57f9db
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/lease_change.go
@@ -0,0 +1,117 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type ChangeLeaseInput struct {
+ ExistingLeaseID string
+ ProposedLeaseID string
+}
+
+type ChangeLeaseResponse struct {
+ autorest.Response
+
+ LeaseID string
+}
+
+// ChangeLease changes an existing lock on a blob for another lock.
+func (client Client) ChangeLease(ctx context.Context, accountName, containerName, blobName string, input ChangeLeaseInput) (result ChangeLeaseResponse, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "ChangeLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "ChangeLease", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "ChangeLease", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "ChangeLease", "`blobName` cannot be an empty string.")
+ }
+ if input.ExistingLeaseID == "" {
+ return result, validation.NewError("blobs.Client", "ChangeLease", "`input.ExistingLeaseID` cannot be an empty string.")
+ }
+ if input.ProposedLeaseID == "" {
+ return result, validation.NewError("blobs.Client", "ChangeLease", "`input.ProposedLeaseID` cannot be an empty string.")
+ }
+
+ req, err := client.ChangeLeasePreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "ChangeLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ChangeLeaseSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "ChangeLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ChangeLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "ChangeLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ChangeLeasePreparer prepares the ChangeLease request.
+func (client Client) ChangeLeasePreparer(ctx context.Context, accountName, containerName, blobName string, input ChangeLeaseInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "change",
+ "x-ms-lease-id": input.ExistingLeaseID,
+ "x-ms-proposed-lease-id": input.ProposedLeaseID,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ChangeLeaseSender sends the ChangeLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ChangeLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ChangeLeaseResponder handles the response to the ChangeLease request. The method always
+// closes the http.Response Body.
+func (client Client) ChangeLeaseResponder(resp *http.Response) (result ChangeLeaseResponse, err error) {
+ if resp != nil && resp.Header != nil {
+ result.LeaseID = resp.Header.Get("x-ms-lease-id")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/lease_release.go b/storage/2017-07-29/blob/blobs/lease_release.go
new file mode 100644
index 0000000..0226cdf
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/lease_release.go
@@ -0,0 +1,98 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// ReleaseLease releases a lock based on the Lease ID.
+func (client Client) ReleaseLease(ctx context.Context, accountName, containerName, blobName, leaseID string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "ReleaseLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "ReleaseLease", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "ReleaseLease", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "ReleaseLease", "`blobName` cannot be an empty string.")
+ }
+ if leaseID == "" {
+ return result, validation.NewError("blobs.Client", "ReleaseLease", "`leaseID` cannot be an empty string.")
+ }
+
+ req, err := client.ReleaseLeasePreparer(ctx, accountName, containerName, blobName, leaseID)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "ReleaseLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ReleaseLeaseSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "ReleaseLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ReleaseLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "ReleaseLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ReleaseLeasePreparer prepares the ReleaseLease request.
+func (client Client) ReleaseLeasePreparer(ctx context.Context, accountName, containerName, blobName, leaseID string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "release",
+ "x-ms-lease-id": leaseID,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ReleaseLeaseSender sends the ReleaseLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ReleaseLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ReleaseLeaseResponder handles the response to the ReleaseLease request. The method always
+// closes the http.Response Body.
+func (client Client) ReleaseLeaseResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/lease_renew.go b/storage/2017-07-29/blob/blobs/lease_renew.go
new file mode 100644
index 0000000..69c495b
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/lease_renew.go
@@ -0,0 +1,97 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+func (client Client) RenewLease(ctx context.Context, accountName, containerName, blobName, leaseID string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "RenewLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "RenewLease", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "RenewLease", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "RenewLease", "`blobName` cannot be an empty string.")
+ }
+ if leaseID == "" {
+ return result, validation.NewError("blobs.Client", "RenewLease", "`leaseID` cannot be an empty string.")
+ }
+
+ req, err := client.RenewLeasePreparer(ctx, accountName, containerName, blobName, leaseID)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "RenewLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.RenewLeaseSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "RenewLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.RenewLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "RenewLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// RenewLeasePreparer prepares the RenewLease request.
+func (client Client) RenewLeasePreparer(ctx context.Context, accountName, containerName, blobName, leaseID string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "renew",
+ "x-ms-lease-id": leaseID,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// RenewLeaseSender sends the RenewLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) RenewLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// RenewLeaseResponder handles the response to the RenewLease request. The method always
+// closes the http.Response Body.
+func (client Client) RenewLeaseResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/lease_test.go b/storage/2017-07-29/blob/blobs/lease_test.go
new file mode 100644
index 0000000..e450d15
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/lease_test.go
@@ -0,0 +1,106 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/blob/containers"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestLeaseLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "ubuntu.iso"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithAuthorizer(containersClient.Client, storageAuth)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Copying file to Blob Storage..")
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+
+ refreshInterval := 5 * time.Second
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, fileName, copyInput, refreshInterval); err != nil {
+ t.Fatalf("Error copying: %s", err)
+ }
+ defer blobClient.Delete(ctx, accountName, containerName, fileName, DeleteInput{})
+
+ // Test begins here
+ t.Logf("[DEBUG] Acquiring Lease..")
+ leaseInput := AcquireLeaseInput{
+ LeaseDuration: -1,
+ }
+ leaseInfo, err := blobClient.AcquireLease(ctx, accountName, containerName, fileName, leaseInput)
+ if err != nil {
+ t.Fatalf("Error acquiring lease: %s", err)
+ }
+ t.Logf("[DEBUG] Lease ID: %q", leaseInfo.LeaseID)
+
+ t.Logf("[DEBUG] Changing Lease..")
+ changeLeaseInput := ChangeLeaseInput{
+ ExistingLeaseID: leaseInfo.LeaseID,
+ ProposedLeaseID: "31f5bb01-cdd9-4166-bcdc-95186076bde0",
+ }
+ changeLeaseResult, err := blobClient.ChangeLease(ctx, accountName, containerName, fileName, changeLeaseInput)
+ if err != nil {
+ t.Fatalf("Error changing lease: %s", err)
+ }
+ t.Logf("[DEBUG] New Lease ID: %q", changeLeaseResult.LeaseID)
+
+ t.Logf("[DEBUG] Releasing Lease..")
+ if _, err := blobClient.ReleaseLease(ctx, accountName, containerName, fileName, changeLeaseResult.LeaseID); err != nil {
+ t.Fatalf("Error releasing lease: %s", err)
+ }
+
+ t.Logf("[DEBUG] Acquiring a new lease..")
+ leaseInput = AcquireLeaseInput{
+ LeaseDuration: 30,
+ }
+ leaseInfo, err = blobClient.AcquireLease(ctx, accountName, containerName, fileName, leaseInput)
+ if err != nil {
+ t.Fatalf("Error acquiring lease: %s", err)
+ }
+ t.Logf("[DEBUG] Lease ID: %q", leaseInfo.LeaseID)
+
+ t.Logf("[DEBUG] Renewing lease..")
+ if _, err := blobClient.RenewLease(ctx, accountName, containerName, fileName, leaseInfo.LeaseID); err != nil {
+ t.Fatalf("Error renewing lease: %s", err)
+ }
+
+ t.Logf("[DEBUG] Breaking lease..")
+ breakLeaseInput := BreakLeaseInput{
+ LeaseID: leaseInfo.LeaseID,
+ }
+ if _, err := blobClient.BreakLease(ctx, accountName, containerName, fileName, breakLeaseInput); err != nil {
+ t.Fatalf("Error breaking lease: %s", err)
+ }
+}
diff --git a/storage/2017-07-29/blob/blobs/lifecycle_test.go b/storage/2017-07-29/blob/blobs/lifecycle_test.go
new file mode 100644
index 0000000..6b8cb59
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/lifecycle_test.go
@@ -0,0 +1,158 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/blob/containers"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "example.txt"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithAuthorizer(containersClient.Client, storageAuth)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Copying file to Blob Storage..")
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+
+ refreshInterval := 5 * time.Second
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, fileName, copyInput, refreshInterval); err != nil {
+ t.Fatalf("Error copying: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Blob Properties..")
+ details, err := blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error retrieving properties: %s", err)
+ }
+
+ // default value
+ if details.AccessTier != Hot {
+ t.Fatalf("Expected the AccessTier to be %q but got %q", Hot, details.AccessTier)
+ }
+ if details.BlobType != BlockBlob {
+ t.Fatalf("Expected BlobType to be %q but got %q", BlockBlob, details.BlobType)
+ }
+ if len(details.MetaData) != 0 {
+ t.Fatalf("Expected there to be no items of metadata but got %d", len(details.MetaData))
+ }
+
+ t.Logf("[DEBUG] Checking it's returned in the List API..")
+ listInput := containers.ListBlobsInput{}
+ listResult, err := containersClient.ListBlobs(ctx, accountName, containerName, listInput)
+ if err != nil {
+ t.Fatalf("Error listing blobs: %s", err)
+ }
+
+ if len(listResult.Blobs.Blobs) != 1 {
+ t.Fatalf("Expected there to be 1 blob in the container but got %d", len(listResult.Blobs.Blobs))
+ }
+
+ t.Logf("[DEBUG] Setting MetaData..")
+ metaDataInput := SetMetaDataInput{
+ MetaData: map[string]string{
+ "hello": "there",
+ },
+ }
+ if _, err := blobClient.SetMetaData(ctx, accountName, containerName, fileName, metaDataInput); err != nil {
+ t.Fatalf("Error setting MetaData: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-retrieving Blob Properties..")
+ details, err = blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error re-retrieving properties: %s", err)
+ }
+
+ // default value
+ if details.AccessTier != Hot {
+ t.Fatalf("Expected the AccessTier to be %q but got %q", Hot, details.AccessTier)
+ }
+ if details.BlobType != BlockBlob {
+ t.Fatalf("Expected BlobType to be %q but got %q", BlockBlob, details.BlobType)
+ }
+ if len(details.MetaData) != 1 {
+ t.Fatalf("Expected there to be 1 item of metadata but got %d", len(details.MetaData))
+ }
+ if details.MetaData["hello"] != "there" {
+ t.Fatalf("Expected `hello` to be `there` but got %q", details.MetaData["there"])
+ }
+
+ t.Logf("[DEBUG] Retrieving the Block List..")
+ getBlockListInput := GetBlockListInput{
+ BlockListType: All,
+ }
+ blockList, err := blobClient.GetBlockList(ctx, accountName, containerName, fileName, getBlockListInput)
+ if err != nil {
+ t.Fatalf("Error retrieving Block List: %s", err)
+ }
+
+ // since this is a copy from an existing file, all blocks should be present
+ if len(blockList.CommittedBlocks.Blocks) == 0 {
+ t.Fatalf("Expected there to be committed blocks but there weren't!")
+ }
+ if len(blockList.UncommittedBlocks.Blocks) != 0 {
+ t.Fatalf("Expected all blocks to be committed but got %d uncommitted blocks", len(blockList.UncommittedBlocks.Blocks))
+ }
+
+ t.Logf("[DEBUG] Changing the Access Tiers..")
+ tiers := []AccessTier{
+ Hot,
+ Cool,
+ Archive,
+ }
+ for _, tier := range tiers {
+ t.Logf("[DEBUG] Updating the Access Tier to %q..", string(tier))
+ if _, err := blobClient.SetTier(ctx, accountName, containerName, fileName, tier); err != nil {
+ t.Fatalf("Error setting the Access Tier: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-retrieving Blob Properties..")
+ details, err = blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error re-retrieving properties: %s", err)
+ }
+
+ if details.AccessTier != tier {
+ t.Fatalf("Expected the AccessTier to be %q but got %q", tier, details.AccessTier)
+ }
+ }
+
+ t.Logf("[DEBUG] Deleting Blob")
+ if _, err := blobClient.Delete(ctx, accountName, containerName, fileName, DeleteInput{}); err != nil {
+ t.Fatalf("Error deleting Blob: %s", err)
+ }
+}
diff --git a/storage/2017-07-29/blob/blobs/metadata_set.go b/storage/2017-07-29/blob/blobs/metadata_set.go
new file mode 100644
index 0000000..ec69152
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/metadata_set.go
@@ -0,0 +1,113 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type SetMetaDataInput struct {
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+
+ // Any metadata which should be added to this blob
+ MetaData map[string]string
+}
+
+// SetMetaData marks the specified blob or snapshot for deletion. The blob is later deleted during garbage collection.
+func (client Client) SetMetaData(ctx context.Context, accountName, containerName, blobName string, input SetMetaDataInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`blobName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("blobs.Client", "GetProperties", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.SetMetaDataPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetMetaDataSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetMetaDataPreparer prepares the SetMetaData request.
+func (client Client) SetMetaDataPreparer(ctx context.Context, accountName, containerName, blobName string, input SetMetaDataInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetMetaDataSender sends the SetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetMetaDataResponder handles the response to the SetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) SetMetaDataResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/models.go b/storage/2017-07-29/blob/blobs/models.go
new file mode 100644
index 0000000..d7d83aa
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/models.go
@@ -0,0 +1,82 @@
+package blobs
+
+type AccessTier string
+
+var (
+ Archive AccessTier = "Archive"
+ Cool AccessTier = "Cool"
+ Hot AccessTier = "Hot"
+)
+
+type ArchiveStatus string
+
+var (
+ None ArchiveStatus = ""
+ RehydratePendingToCool ArchiveStatus = "rehydrate-pending-to-cool"
+ RehydratePendingToHot ArchiveStatus = "rehydrate-pending-to-hot"
+)
+
+type BlockListType string
+
+var (
+ All BlockListType = "all"
+ Committed BlockListType = "committed"
+ Uncommitted BlockListType = "uncommitted"
+)
+
+type Block struct {
+ // The base64-encoded Block ID
+ Name string `xml:"Name"`
+
+ // The size of the Block in Bytes
+ Size int64 `xml:"Size"`
+}
+
+type BlobType string
+
+var (
+ AppendBlob BlobType = "AppendBlob"
+ BlockBlob BlobType = "BlockBlob"
+ PageBlob BlobType = "PageBlob"
+)
+
+type CommittedBlocks struct {
+ Blocks []Block `xml:"Block"`
+}
+
+type CopyStatus string
+
+var (
+ Aborted CopyStatus = "aborted"
+ Failed CopyStatus = "failed"
+ Pending CopyStatus = "pending"
+ Success CopyStatus = "success"
+)
+
+type LeaseDuration string
+
+var (
+ Fixed LeaseDuration = "fixed"
+ Infinite LeaseDuration = "infinite"
+)
+
+type LeaseState string
+
+var (
+ Available LeaseState = "available"
+ Breaking LeaseState = "breaking"
+ Broken LeaseState = "broken"
+ Expired LeaseState = "expired"
+ Leased LeaseState = "leased"
+)
+
+type LeaseStatus string
+
+var (
+ Locked LeaseStatus = "locked"
+ Unlocked LeaseStatus = "unlocked"
+)
+
+type UncommittedBlocks struct {
+ Blocks []Block `xml:"Block"`
+}
diff --git a/storage/2017-07-29/blob/blobs/properties_get.go b/storage/2017-07-29/blob/blobs/properties_get.go
new file mode 100644
index 0000000..de7c5fc
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/properties_get.go
@@ -0,0 +1,310 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetPropertiesInput struct {
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+}
+
+type GetPropertiesResult struct {
+ autorest.Response
+
+ // The tier of page blob on a premium storage account or tier of block blob on blob storage or general purpose v2 account.
+ AccessTier AccessTier
+
+ // This gives the last time tier was changed on the object.
+ // This header is returned only if tier on block blob was ever set.
+ // The date format follows RFC 1123
+ AccessTierChangeTime string
+
+ // For page blobs on a premium storage account only.
+ // If the access tier is not explicitly set on the blob, the tier is inferred based on its content length
+ // and this header will be returned with true value.
+ // For block blobs on Blob Storage or general purpose v2 account, if the blob does not have the access tier
+ // set then we infer the tier from the storage account properties. This header is set only if the block blob
+ // tier is inferred
+ AccessTierInferred bool
+
+ // For blob storage or general purpose v2 account.
+ // If the blob is being rehydrated and is not complete then this header is returned indicating
+ // that rehydrate is pending and also tells the destination tier
+ ArchiveStatus ArchiveStatus
+
+ // The number of committed blocks present in the blob.
+ // This header is returned only for append blobs.
+ BlobCommittedBlockCount string
+
+ // The current sequence number for a page blob.
+ // This header is not returned for block blobs or append blobs.
+ // This header is not returned for block blobs.
+ BlobSequenceNumber string
+
+ // The blob type.
+ BlobType BlobType
+
+ // If the Cache-Control request header has previously been set for the blob, that value is returned in this header.
+ CacheControl string
+
+ // The Content-Disposition response header field conveys additional information about how to process
+ // the response payload, and also can be used to attach additional metadata.
+ // For example, if set to attachment, it indicates that the user-agent should not display the response,
+ // but instead show a Save As dialog.
+ ContentDisposition string
+
+ // If the Content-Encoding request header has previously been set for the blob,
+ // that value is returned in this header.
+ ContentEncoding string
+
+ // If the Content-Language request header has previously been set for the blob,
+ // that value is returned in this header.
+ ContentLanguage string
+
+ // The size of the blob in bytes.
+ // For a page blob, this header returns the value of the x-ms-blob-content-length header stored with the blob.
+ ContentLength int64
+
+ // The content type specified for the blob.
+ // If no content type was specified, the default content type is `application/octet-stream`.
+ ContentType string
+
+ // If the Content-MD5 header has been set for the blob, this response header is returned so that
+ // the client can check for message content integrity.
+ ContentMD5 string
+
+ // Conclusion time of the last attempted Copy Blob operation where this blob was the destination blob.
+ // This value can specify the time of a completed, aborted, or failed copy attempt.
+ // This header does not appear if a copy is pending, if this blob has never been the
+ // destination in a Copy Blob operation, or if this blob has been modified after a concluded Copy Blob
+ // operation using Set Blob Properties, Put Blob, or Put Block List.
+ CopyCompletionTime string
+
+ // Included if the blob is incremental copy blob or incremental copy snapshot, if x-ms-copy-status is success.
+ // Snapshot time of the last successful incremental copy snapshot for this blob
+ CopyDestinationSnapshot string
+
+ // String identifier for the last attempted Copy Blob operation where this blob was the destination blob.
+ // This header does not appear if this blob has never been the destination in a Copy Blob operation,
+ // or if this blob has been modified after a concluded Copy Blob operation using Set Blob Properties,
+ // Put Blob, or Put Block List.
+ CopyID string
+
+ // Contains the number of bytes copied and the total bytes in the source in the last attempted
+ // Copy Blob operation where this blob was the destination blob.
+ // Can show between 0 and Content-Length bytes copied.
+ // This header does not appear if this blob has never been the destination in a Copy Blob operation,
+ // or if this blob has been modified after a concluded Copy Blob operation using Set Blob Properties,
+ // Put Blob, or Put Block List.
+ CopyProgress string
+
+ // URL up to 2 KB in length that specifies the source blob used in the last attempted Copy Blob operation
+ // where this blob was the destination blob.
+ // This header does not appear if this blob has never been the destination in a Copy Blob operation,
+ // or if this blob has been modified after a concluded Copy Blob operation using Set Blob Properties,
+ // Put Blob, or Put Block List
+ CopySource string
+
+ // State of the copy operation identified by x-ms-copy-id, with these values:
+ // - success: Copy completed successfully.
+ // - pending: Copy is in progress.
+ // Check x-ms-copy-status-description if intermittent, non-fatal errors
+ // impede copy progress but don’t cause failure.
+ // - aborted: Copy was ended by Abort Copy Blob.
+ // - failed: Copy failed. See x-ms- copy-status-description for failure details.
+ // This header does not appear if this blob has never been the destination in a Copy Blob operation,
+ // or if this blob has been modified after a completed Copy Blob operation using Set Blob Properties,
+ // Put Blob, or Put Block List.
+ CopyStatus CopyStatus
+
+ // Describes cause of fatal or non-fatal copy operation failure.
+ // This header does not appear if this blob has never been the destination in a Copy Blob operation,
+ // or if this blob has been modified after a concluded Copy Blob operation using Set Blob Properties,
+ // Put Blob, or Put Block List.
+ CopyStatusDescription string
+
+ // The date/time at which the blob was created. The date format follows RFC 1123
+ CreationTime string
+
+ // The ETag contains a value that you can use to perform operations conditionally
+ ETag string
+
+ // Included if the blob is incremental copy blob.
+ IncrementalCopy bool
+
+ // The date/time that the blob was last modified. The date format follows RFC 1123.
+ LastModified string
+
+ // When a blob is leased, specifies whether the lease is of infinite or fixed duration
+ LeaseDuration LeaseDuration
+
+ // The lease state of the blob
+ LeaseState LeaseState
+
+ LeaseStatus LeaseStatus
+
+ // A set of name-value pairs that correspond to the user-defined metadata associated with this blob
+ MetaData map[string]string
+
+ // Is the Storage Account encrypted using server-side encryption? This should always return true
+ ServerEncrypted bool
+}
+
+// GetProperties returns all user-defined metadata, standard HTTP properties, and system properties for the blob
+func (client Client) GetProperties(ctx context.Context, accountName, containerName, blobName string, input GetPropertiesInput) (result GetPropertiesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.GetPropertiesPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetPropertiesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPropertiesPreparer prepares the GetProperties request.
+func (client Client) GetPropertiesPreparer(ctx context.Context, accountName, containerName, blobName string, input GetPropertiesInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsHead(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetPropertiesSender sends the GetProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetPropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetPropertiesResponder handles the response to the GetProperties request. The method always
+// closes the http.Response Body.
+func (client Client) GetPropertiesResponder(resp *http.Response) (result GetPropertiesResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.AccessTier = AccessTier(resp.Header.Get("x-ms-access-tier"))
+ result.AccessTierChangeTime = resp.Header.Get(" x-ms-access-tier-change-time")
+ result.ArchiveStatus = ArchiveStatus(resp.Header.Get(" x-ms-archive-status"))
+ result.BlobCommittedBlockCount = resp.Header.Get("x-ms-blob-committed-block-count")
+ result.BlobSequenceNumber = resp.Header.Get("x-ms-blob-sequence-number")
+ result.BlobType = BlobType(resp.Header.Get("x-ms-blob-type"))
+ result.CacheControl = resp.Header.Get("Cache-Control")
+ result.ContentDisposition = resp.Header.Get("Content-Disposition")
+ result.ContentEncoding = resp.Header.Get("Content-Encoding")
+ result.ContentLanguage = resp.Header.Get("Content-Language")
+ result.ContentMD5 = resp.Header.Get("Content-MD5")
+ result.ContentType = resp.Header.Get("Content-Type")
+ result.CopyCompletionTime = resp.Header.Get("x-ms-copy-completion-time")
+ result.CopyDestinationSnapshot = resp.Header.Get("x-ms-copy-destination-snapshot")
+ result.CopyID = resp.Header.Get("x-ms-copy-id")
+ result.CopyProgress = resp.Header.Get(" x-ms-copy-progress")
+ result.CopySource = resp.Header.Get("x-ms-copy-source")
+ result.CopyStatus = CopyStatus(resp.Header.Get("x-ms-copy-status"))
+ result.CopyStatusDescription = resp.Header.Get("x-ms-copy-status-description")
+ result.CreationTime = resp.Header.Get("x-ms-creation-time")
+ result.ETag = resp.Header.Get("Etag")
+ result.LastModified = resp.Header.Get("Last-Modified")
+ result.LeaseDuration = LeaseDuration(resp.Header.Get("x-ms-lease-duration"))
+ result.LeaseState = LeaseState(resp.Header.Get("x-ms-lease-state"))
+ result.LeaseStatus = LeaseStatus(resp.Header.Get("x-ms-lease-status"))
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+
+ if v := resp.Header.Get("x-ms-access-tier-inferred"); v != "" {
+ b, innerErr := strconv.ParseBool(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as a bool: %s", v, innerErr)
+ return
+ }
+
+ result.AccessTierInferred = b
+ }
+
+ if v := resp.Header.Get("Content-Length"); v != "" {
+ i, innerErr := strconv.Atoi(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as an integer: %s", v, innerErr)
+ }
+
+ result.ContentLength = int64(i)
+ }
+
+ if v := resp.Header.Get("x-ms-incremental-copy"); v != "" {
+ b, innerErr := strconv.ParseBool(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as a bool: %s", v, innerErr)
+ return
+ }
+
+ result.IncrementalCopy = b
+ }
+
+ if v := resp.Header.Get("x-ms-server-encrypted"); v != "" {
+ b, innerErr := strconv.ParseBool(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as a bool: %s", v, innerErr)
+ return
+ }
+
+ result.IncrementalCopy = b
+ }
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/properties_set.go b/storage/2017-07-29/blob/blobs/properties_set.go
new file mode 100644
index 0000000..a8c0ed8
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/properties_set.go
@@ -0,0 +1,156 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type SetPropertiesInput struct {
+ CacheControl *string
+ ContentType *string
+ ContentMD5 *string
+ ContentEncoding *string
+ ContentLanguage *string
+ LeaseID *string
+ ContentDisposition *string
+ ContentLength *int64
+ SequenceNumberAction *SequenceNumberAction
+ BlobSequenceNumber *string
+}
+
+type SetPropertiesResult struct {
+ autorest.Response
+
+ BlobSequenceNumber string
+ Etag string
+}
+
+// SetProperties sets system properties on the blob.
+func (client Client) SetProperties(ctx context.Context, accountName, containerName, blobName string, input SetPropertiesInput) (result SetPropertiesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "SetProperties", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "SetProperties", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "SetProperties", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "SetProperties", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.SetPropertiesPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetPropertiesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+type SequenceNumberAction string
+
+var (
+ Increment SequenceNumberAction = "increment"
+ Max SequenceNumberAction = "max"
+ Update SequenceNumberAction = "update"
+)
+
+// SetPropertiesPreparer prepares the SetProperties request.
+func (client Client) SetPropertiesPreparer(ctx context.Context, accountName, containerName, blobName string, input SetPropertiesInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "properties"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.CacheControl != nil {
+ headers["x-ms-blob-cache-control"] = *input.CacheControl
+ }
+ if input.ContentDisposition != nil {
+ headers["x-ms-blob-content-disposition"] = *input.ContentDisposition
+ }
+ if input.ContentEncoding != nil {
+ headers["x-ms-blob-content-encoding"] = *input.ContentEncoding
+ }
+ if input.ContentLanguage != nil {
+ headers["x-ms-blob-content-language"] = *input.ContentLanguage
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.ContentType != nil {
+ headers["x-ms-blob-content-type"] = *input.ContentType
+ }
+ if input.ContentLength != nil {
+ headers["x-ms-blob-content-length"] = *input.ContentLength
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+ if input.SequenceNumberAction != nil {
+ headers["x-ms-sequence-number-action"] = string(*input.SequenceNumberAction)
+ }
+ if input.BlobSequenceNumber != nil {
+ headers["x-ms-blob-sequence-number"] = *input.BlobSequenceNumber
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetPropertiesSender sends the SetProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetPropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetPropertiesResponder handles the response to the SetProperties request. The method always
+// closes the http.Response Body.
+func (client Client) SetPropertiesResponder(resp *http.Response) (result SetPropertiesResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.BlobSequenceNumber = resp.Header.Get("x-ms-blob-sequence-number")
+ result.Etag = resp.Header.Get("Etag")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/put_append_blob.go b/storage/2017-07-29/blob/blobs/put_append_blob.go
new file mode 100644
index 0000000..ef2c502
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/put_append_blob.go
@@ -0,0 +1,134 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type PutAppendBlobInput struct {
+ CacheControl *string
+ ContentDisposition *string
+ ContentEncoding *string
+ ContentLanguage *string
+ ContentMD5 *string
+ ContentType *string
+ LeaseID *string
+ MetaData map[string]string
+}
+
+// PutAppendBlob is a wrapper around the Put API call (with a stricter input object)
+// which creates a new append blob, or updates the content of an existing blob.
+func (client Client) PutAppendBlob(ctx context.Context, accountName, containerName, blobName string, input PutAppendBlobInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutAppendBlob", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutAppendBlob", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutAppendBlob", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutAppendBlob", "`blobName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("blobs.Client", "PutAppendBlob", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.PutAppendBlobPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutAppendBlob", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutAppendBlobSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutAppendBlob", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutAppendBlobResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutAppendBlob", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutAppendBlobPreparer prepares the PutAppendBlob request.
+func (client Client) PutAppendBlobPreparer(ctx context.Context, accountName, containerName, blobName string, input PutAppendBlobInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-blob-type": string(AppendBlob),
+ "x-ms-version": APIVersion,
+
+ // For a page blob or an append blob, the value of this header must be set to zero,
+ // as Put Blob is used only to initialize the blob
+ "Content-Length": 0,
+ }
+
+ if input.CacheControl != nil {
+ headers["x-ms-blob-cache-control"] = *input.CacheControl
+ }
+ if input.ContentDisposition != nil {
+ headers["x-ms-blob-content-disposition"] = *input.ContentDisposition
+ }
+ if input.ContentEncoding != nil {
+ headers["x-ms-blob-content-encoding"] = *input.ContentEncoding
+ }
+ if input.ContentLanguage != nil {
+ headers["x-ms-blob-content-language"] = *input.ContentLanguage
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.ContentType != nil {
+ headers["x-ms-blob-content-type"] = *input.ContentType
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutAppendBlobSender sends the PutAppendBlob request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutAppendBlobSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutAppendBlobResponder handles the response to the PutAppendBlob request. The method always
+// closes the http.Response Body.
+func (client Client) PutAppendBlobResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/put_block.go b/storage/2017-07-29/blob/blobs/put_block.go
new file mode 100644
index 0000000..5256013
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/put_block.go
@@ -0,0 +1,125 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type PutBlockInput struct {
+ BlockID string
+ Content []byte
+ ContentMD5 *string
+ LeaseID *string
+}
+
+type PutBlockResult struct {
+ autorest.Response
+
+ ContentMD5 string
+}
+
+// PutBlock creates a new block to be committed as part of a blob.
+func (client Client) PutBlock(ctx context.Context, accountName, containerName, blobName string, input PutBlockInput) (result PutBlockResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlock", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlock", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutBlock", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlock", "`blobName` cannot be an empty string.")
+ }
+ if input.BlockID == "" {
+ return result, validation.NewError("blobs.Client", "PutBlock", "`input.BlockID` cannot be an empty string.")
+ }
+ if len(input.Content) == 0 {
+ return result, validation.NewError("blobs.Client", "PutBlock", "`input.Content` cannot be empty.")
+ }
+
+ req, err := client.PutBlockPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlock", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutBlockSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlock", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutBlockResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlock", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutBlockPreparer prepares the PutBlock request.
+func (client Client) PutBlockPreparer(ctx context.Context, accountName, containerName, blobName string, input PutBlockInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "block"),
+ "blockid": autorest.Encode("query", input.BlockID),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithBytes(&input.Content))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutBlockSender sends the PutBlock request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutBlockSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutBlockResponder handles the response to the PutBlock request. The method always
+// closes the http.Response Body.
+func (client Client) PutBlockResponder(resp *http.Response) (result PutBlockResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.ContentMD5 = resp.Header.Get("Content-MD5")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/put_block_blob.go b/storage/2017-07-29/blob/blobs/put_block_blob.go
new file mode 100644
index 0000000..fa29dd3
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/put_block_blob.go
@@ -0,0 +1,135 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type PutBlockBlobInput struct {
+ CacheControl *string
+ Content []byte
+ ContentDisposition *string
+ ContentEncoding *string
+ ContentLanguage *string
+ ContentMD5 *string
+ ContentType *string
+ LeaseID *string
+ MetaData map[string]string
+}
+
+// PutBlockBlob is a wrapper around the Put API call (with a stricter input object)
+// which creates a new block append blob, or updates the content of an existing block blob.
+func (client Client) PutBlockBlob(ctx context.Context, accountName, containerName, blobName string, input PutBlockBlobInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockBlob", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockBlob", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutBlockBlob", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockBlob", "`blobName` cannot be an empty string.")
+ }
+ if len(input.Content) == 0 {
+ return result, validation.NewError("blobs.Client", "PutBlockBlob", "`input.Content` cannot be empty.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("blobs.Client", "PutBlockBlob", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.PutBlockBlobPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockBlob", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutBlockBlobSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockBlob", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutBlockBlobResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockBlob", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutBlockBlobPreparer prepares the PutBlockBlob request.
+func (client Client) PutBlockBlobPreparer(ctx context.Context, accountName, containerName, blobName string, input PutBlockBlobInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-blob-type": string(BlockBlob),
+ "x-ms-version": APIVersion,
+ }
+
+ if input.CacheControl != nil {
+ headers["x-ms-blob-cache-control"] = *input.CacheControl
+ }
+ if input.ContentDisposition != nil {
+ headers["x-ms-blob-content-disposition"] = *input.ContentDisposition
+ }
+ if input.ContentEncoding != nil {
+ headers["x-ms-blob-content-encoding"] = *input.ContentEncoding
+ }
+ if input.ContentLanguage != nil {
+ headers["x-ms-blob-content-language"] = *input.ContentLanguage
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.ContentType != nil {
+ headers["x-ms-blob-content-type"] = *input.ContentType
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithBytes(&input.Content))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutBlockBlobSender sends the PutBlockBlob request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutBlockBlobSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutBlockBlobResponder handles the response to the PutBlockBlob request. The method always
+// closes the http.Response Body.
+func (client Client) PutBlockBlobResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/put_block_blob_file.go b/storage/2017-07-29/blob/blobs/put_block_blob_file.go
new file mode 100644
index 0000000..7232e5e
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/put_block_blob_file.go
@@ -0,0 +1,34 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "os"
+)
+
+// PutBlockBlobFromFile is a helper method which takes a file, and automatically chunks it up, rather than having to do this yourself
+func (client Client) PutBlockBlobFromFile(ctx context.Context, accountName, containerName, blobName string, file *os.File, input PutBlockBlobInput) error {
+ fileInfo, err := file.Stat()
+ if err != nil {
+ return fmt.Errorf("Error loading file info: %s", err)
+ }
+
+ fileSize := fileInfo.Size()
+ bytes := make([]byte, fileSize)
+
+ _, err = file.ReadAt(bytes, 0)
+ if err != nil {
+ if err != io.EOF {
+ return fmt.Errorf("Error reading bytes: %s", err)
+ }
+ }
+
+ input.Content = bytes
+
+ if _, err = client.PutBlockBlob(ctx, accountName, containerName, blobName, input); err != nil {
+ return fmt.Errorf("Error putting bytes: %s", err)
+ }
+
+ return nil
+}
diff --git a/storage/2017-07-29/blob/blobs/put_block_list.go b/storage/2017-07-29/blob/blobs/put_block_list.go
new file mode 100644
index 0000000..f805247
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/put_block_list.go
@@ -0,0 +1,157 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type BlockList struct {
+ CommittedBlockIDs []BlockID `xml:"Committed,omitempty"`
+ UncommittedBlockIDs []BlockID `xml:"Uncommitted,omitempty"`
+ LatestBlockIDs []BlockID `xml:"Latest,omitempty"`
+}
+
+type BlockID struct {
+ Value string `xml:",chardata"`
+}
+
+type PutBlockListInput struct {
+ BlockList BlockList
+ CacheControl *string
+ ContentDisposition *string
+ ContentEncoding *string
+ ContentLanguage *string
+ ContentMD5 *string
+ ContentType *string
+ MetaData map[string]string
+ LeaseID *string
+}
+
+type PutBlockListResult struct {
+ autorest.Response
+
+ ContentMD5 string
+ ETag string
+ LastModified string
+}
+
+// PutBlockList writes a blob by specifying the list of block IDs that make up the blob.
+// In order to be written as part of a blob, a block must have been successfully written
+// to the server in a prior Put Block operation.
+func (client Client) PutBlockList(ctx context.Context, accountName, containerName, blobName string, input PutBlockListInput) (result PutBlockListResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockList", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockList", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutBlockList", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockList", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.PutBlockListPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockList", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutBlockListSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockList", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutBlockListResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockList", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutBlockListPreparer prepares the PutBlockList request.
+func (client Client) PutBlockListPreparer(ctx context.Context, accountName, containerName, blobName string, input PutBlockListInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "blocklist"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.CacheControl != nil {
+ headers["x-ms-blob-cache-control"] = *input.CacheControl
+ }
+ if input.ContentDisposition != nil {
+ headers["x-ms-blob-content-disposition"] = *input.ContentDisposition
+ }
+ if input.ContentEncoding != nil {
+ headers["x-ms-blob-content-encoding"] = *input.ContentEncoding
+ }
+ if input.ContentLanguage != nil {
+ headers["x-ms-blob-content-language"] = *input.ContentLanguage
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.ContentType != nil {
+ headers["x-ms-blob-content-type"] = *input.ContentType
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithXML(input.BlockList))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutBlockListSender sends the PutBlockList request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutBlockListSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutBlockListResponder handles the response to the PutBlockList request. The method always
+// closes the http.Response Body.
+func (client Client) PutBlockListResponder(resp *http.Response) (result PutBlockListResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.ContentMD5 = resp.Header.Get("Content-MD5")
+ result.ETag = resp.Header.Get("ETag")
+ result.LastModified = resp.Header.Get("Last-Modified")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/put_page_blob.go b/storage/2017-07-29/blob/blobs/put_page_blob.go
new file mode 100644
index 0000000..ad3c878
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/put_page_blob.go
@@ -0,0 +1,148 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type PutPageBlobInput struct {
+ CacheControl *string
+ ContentDisposition *string
+ ContentEncoding *string
+ ContentLanguage *string
+ ContentMD5 *string
+ ContentType *string
+ LeaseID *string
+ MetaData map[string]string
+
+ BlobContentLengthBytes int64
+ BlobSequenceNumber *int64
+ AccessTier *AccessTier
+}
+
+// PutPageBlob is a wrapper around the Put API call (with a stricter input object)
+// which creates a new block blob, or updates the content of an existing page blob.
+func (client Client) PutPageBlob(ctx context.Context, accountName, containerName, blobName string, input PutPageBlobInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageBlob", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageBlob", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutPageBlob", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageBlob", "`blobName` cannot be an empty string.")
+ }
+ if input.BlobContentLengthBytes == 0 || input.BlobContentLengthBytes%512 != 0 {
+ return result, validation.NewError("blobs.Client", "PutPageBlob", "`blobName` must be aligned to a 512-byte boundary.")
+ }
+
+ req, err := client.PutPageBlobPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageBlob", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutPageBlobSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageBlob", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutPageBlobResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageBlob", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutPageBlobPreparer prepares the PutPageBlob request.
+func (client Client) PutPageBlobPreparer(ctx context.Context, accountName, containerName, blobName string, input PutPageBlobInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-blob-type": string(PageBlob),
+ "x-ms-version": APIVersion,
+
+ // For a page blob or an page blob, the value of this header must be set to zero,
+ // as Put Blob is used only to initialize the blob
+ "Content-Length": 0,
+
+ // This header specifies the maximum size for the page blob, up to 8 TB.
+ // The page blob size must be aligned to a 512-byte boundary.
+ "x-ms-blob-content-length": input.BlobContentLengthBytes,
+ }
+
+ if input.AccessTier != nil {
+ headers["x-ms-access-tier"] = string(*input.AccessTier)
+ }
+ if input.BlobSequenceNumber != nil {
+ headers["x-ms-blob-sequence-number"] = *input.BlobSequenceNumber
+ }
+
+ if input.CacheControl != nil {
+ headers["x-ms-blob-cache-control"] = *input.CacheControl
+ }
+ if input.ContentDisposition != nil {
+ headers["x-ms-blob-content-disposition"] = *input.ContentDisposition
+ }
+ if input.ContentEncoding != nil {
+ headers["x-ms-blob-content-encoding"] = *input.ContentEncoding
+ }
+ if input.ContentLanguage != nil {
+ headers["x-ms-blob-content-language"] = *input.ContentLanguage
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.ContentType != nil {
+ headers["x-ms-blob-content-type"] = *input.ContentType
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutPageBlobSender sends the PutPageBlob request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutPageBlobSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutPageBlobResponder handles the response to the PutPageBlob request. The method always
+// closes the http.Response Body.
+func (client Client) PutPageBlobResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/put_page_clear.go b/storage/2017-07-29/blob/blobs/put_page_clear.go
new file mode 100644
index 0000000..59feaa5
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/put_page_clear.go
@@ -0,0 +1,113 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type PutPageClearInput struct {
+ StartByte int64
+ EndByte int64
+
+ LeaseID *string
+}
+
+// PutPageClear clears a range of pages within a page blob.
+func (client Client) PutPageClear(ctx context.Context, accountName, containerName, blobName string, input PutPageClearInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageClear", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageClear", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutPageClear", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageClear", "`blobName` cannot be an empty string.")
+ }
+ if input.StartByte < 0 {
+ return result, validation.NewError("blobs.Client", "PutPageClear", "`input.StartByte` must be greater than or equal to 0.")
+ }
+ if input.EndByte <= 0 {
+ return result, validation.NewError("blobs.Client", "PutPageClear", "`input.EndByte` must be greater than 0.")
+ }
+
+ req, err := client.PutPageClearPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageClear", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutPageClearSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageClear", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutPageClearResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageClear", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutPageClearPreparer prepares the PutPageClear request.
+func (client Client) PutPageClearPreparer(ctx context.Context, accountName, containerName, blobName string, input PutPageClearInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "page"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-page-write": "clear",
+ "x-ms-range": fmt.Sprintf("bytes=%d-%d", input.StartByte, input.EndByte),
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutPageClearSender sends the PutPageClear request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutPageClearSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutPageClearResponder handles the response to the PutPageClear request. The method always
+// closes the http.Response Body.
+func (client Client) PutPageClearResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/put_page_update.go b/storage/2017-07-29/blob/blobs/put_page_update.go
new file mode 100644
index 0000000..a47e8ca
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/put_page_update.go
@@ -0,0 +1,163 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type PutPageUpdateInput struct {
+ StartByte int64
+ EndByte int64
+ Content []byte
+
+ IfSequenceNumberEQ *string
+ IfSequenceNumberLE *string
+ IfSequenceNumberLT *string
+ IfModifiedSince *string
+ IfUnmodifiedSince *string
+ IfMatch *string
+ IfNoneMatch *string
+ LeaseID *string
+}
+
+type PutPageUpdateResult struct {
+ autorest.Response
+
+ BlobSequenceNumber string
+ ContentMD5 string
+ LastModified string
+}
+
+// PutPageUpdate writes a range of pages to a page blob.
+func (client Client) PutPageUpdate(ctx context.Context, accountName, containerName, blobName string, input PutPageUpdateInput) (result PutPageUpdateResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", "`blobName` cannot be an empty string.")
+ }
+ if input.StartByte < 0 {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", "`input.StartByte` must be greater than or equal to 0.")
+ }
+ if input.EndByte <= 0 {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", "`input.EndByte` must be greater than 0.")
+ }
+
+ expectedSize := (input.EndByte - input.StartByte) + 1
+ actualSize := int64(len(input.Content))
+ if expectedSize != actualSize {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", fmt.Sprintf("Content Size was defined as %d but got %d.", expectedSize, actualSize))
+ }
+
+ req, err := client.PutPageUpdatePreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageUpdate", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutPageUpdateSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageUpdate", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutPageUpdateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageUpdate", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutPageUpdatePreparer prepares the PutPageUpdate request.
+func (client Client) PutPageUpdatePreparer(ctx context.Context, accountName, containerName, blobName string, input PutPageUpdateInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "page"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-page-write": "update",
+ "x-ms-range": fmt.Sprintf("bytes=%d-%d", input.StartByte, input.EndByte),
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+ if input.IfSequenceNumberEQ != nil {
+ headers["x-ms-if-sequence-number-eq"] = *input.IfSequenceNumberEQ
+ }
+ if input.IfSequenceNumberLE != nil {
+ headers["x-ms-if-sequence-number-le"] = *input.IfSequenceNumberLE
+ }
+ if input.IfSequenceNumberLT != nil {
+ headers["x-ms-if-sequence-number-lt"] = *input.IfSequenceNumberLT
+ }
+ if input.IfModifiedSince != nil {
+ headers["If-Modified-Since"] = *input.IfModifiedSince
+ }
+ if input.IfUnmodifiedSince != nil {
+ headers["If-Unmodified-Since"] = *input.IfUnmodifiedSince
+ }
+ if input.IfMatch != nil {
+ headers["If-Match"] = *input.IfMatch
+ }
+ if input.IfNoneMatch != nil {
+ headers["If-None-Match"] = *input.IfNoneMatch
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithBytes(&input.Content))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutPageUpdateSender sends the PutPageUpdate request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutPageUpdateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutPageUpdateResponder handles the response to the PutPageUpdate request. The method always
+// closes the http.Response Body.
+func (client Client) PutPageUpdateResponder(resp *http.Response) (result PutPageUpdateResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.BlobSequenceNumber = resp.Header.Get("x-ms-blob-sequence-number")
+ result.ContentMD5 = resp.Header.Get("Content-MD5")
+ result.LastModified = resp.Header.Get("Last-Modified")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/resource_id.go b/storage/2017-07-29/blob/blobs/resource_id.go
new file mode 100644
index 0000000..0f6dddf
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/resource_id.go
@@ -0,0 +1,56 @@
+package blobs
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Blob
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, containerName, blobName string) string {
+ domain := endpoints.GetBlobEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s/%s", domain, containerName, blobName)
+}
+
+type ResourceID struct {
+ AccountName string
+ ContainerName string
+ BlobName string
+}
+
+// ParseResourceID parses the Resource ID and returns an object which can be used
+// to interact with the Blob Resource
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://foo.blob.core.windows.net/Bar/example.vhd
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ path := strings.TrimPrefix(uri.Path, "/")
+ segments := strings.Split(path, "/")
+ if len(segments) == 0 {
+ return nil, fmt.Errorf("Expected the path to contain segments but got none")
+ }
+
+ containerName := segments[0]
+ blobName := strings.TrimPrefix(path, containerName)
+ blobName = strings.TrimPrefix(blobName, "/")
+ return &ResourceID{
+ AccountName: *accountName,
+ ContainerName: containerName,
+ BlobName: blobName,
+ }, nil
+}
diff --git a/storage/2017-07-29/blob/blobs/resource_id_test.go b/storage/2017-07-29/blob/blobs/resource_id_test.go
new file mode 100644
index 0000000..bb6cad1
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/resource_id_test.go
@@ -0,0 +1,123 @@
+package blobs
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.blob.core.chinacloudapi.cn/container1/blob1.vhd",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.blob.core.cloudapi.de/container1/blob1.vhd",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.blob.core.windows.net/container1/blob1.vhd",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.blob.core.usgovcloudapi.net/container1/blob1.vhd",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "container1", "blob1.vhd")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.blob.core.chinacloudapi.cn/container1/blob1.vhd",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.blob.core.cloudapi.de/container1/blob1.vhd",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.blob.core.windows.net/container1/blob1.vhd",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.blob.core.usgovcloudapi.net/container1/blob1.vhd",
+ },
+ }
+ t.Logf("[DEBUG] Top Level Files")
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.ContainerName != "container1" {
+ t.Fatalf("Expected Container Name to be `container1` but got %q", actual.ContainerName)
+ }
+ if actual.BlobName != "blob1.vhd" {
+ t.Fatalf("Expected Blob Name to be `blob1.vhd` but got %q", actual.BlobName)
+ }
+ }
+
+ testData = []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.blob.core.chinacloudapi.cn/container1/example/blob1.vhd",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.blob.core.cloudapi.de/container1/example/blob1.vhd",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.blob.core.windows.net/container1/example/blob1.vhd",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.blob.core.usgovcloudapi.net/container1/example/blob1.vhd",
+ },
+ }
+ t.Logf("[DEBUG] Nested Files")
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.ContainerName != "container1" {
+ t.Fatalf("Expected Container Name to be `container1` but got %q", actual.ContainerName)
+ }
+ if actual.BlobName != "example/blob1.vhd" {
+ t.Fatalf("Expected Blob Name to be `example/blob1.vhd` but got %q", actual.BlobName)
+ }
+ }
+}
diff --git a/storage/2017-07-29/blob/blobs/set_tier.go b/storage/2017-07-29/blob/blobs/set_tier.go
new file mode 100644
index 0000000..dd0f0b8
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/set_tier.go
@@ -0,0 +1,93 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// SetTier sets the tier on a blob.
+func (client Client) SetTier(ctx context.Context, accountName, containerName, blobName string, tier AccessTier) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "SetTier", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "SetTier", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "SetTier", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "SetTier", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.SetTierPreparer(ctx, accountName, containerName, blobName, tier)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetTier", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetTierSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetTier", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetTierResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetTier", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetTierPreparer prepares the SetTier request.
+func (client Client) SetTierPreparer(ctx context.Context, accountName, containerName, blobName string, tier AccessTier) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "tier"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-access-tier": string(tier),
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetTierSender sends the SetTier request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetTierSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetTierResponder handles the response to the SetTier request. The method always
+// closes the http.Response Body.
+func (client Client) SetTierResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/snapshot.go b/storage/2017-07-29/blob/blobs/snapshot.go
new file mode 100644
index 0000000..180070b
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/snapshot.go
@@ -0,0 +1,163 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type SnapshotInput struct {
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+
+ // MetaData is a user-defined name-value pair associated with the blob.
+ // If no name-value pairs are specified, the operation will copy the base blob metadata to the snapshot.
+ // If one or more name-value pairs are specified, the snapshot is created with the specified metadata,
+ // and metadata is not copied from the base blob.
+ MetaData map[string]string
+
+ // A DateTime value which will only snapshot the blob if it has been modified since the specified date/time
+ // If the base blob has not been modified, the Blob service returns status code 412 (Precondition Failed).
+ IfModifiedSince *string
+
+ // A DateTime value which will only snapshot the blob if it has not been modified since the specified date/time
+ // If the base blob has been modified, the Blob service returns status code 412 (Precondition Failed).
+ IfUnmodifiedSince *string
+
+ // An ETag value to snapshot the blob only if its ETag value matches the value specified.
+ // If the values do not match, the Blob service returns status code 412 (Precondition Failed).
+ IfMatch *string
+
+ // An ETag value for this conditional header to snapshot the blob only if its ETag value
+ // does not match the value specified.
+ // If the values are identical, the Blob service returns status code 412 (Precondition Failed).
+ IfNoneMatch *string
+}
+
+type SnapshotResult struct {
+ autorest.Response
+
+ // The ETag of the snapshot
+ ETag string
+
+ // A DateTime value that uniquely identifies the snapshot.
+ // The value of this header indicates the snapshot version,
+ // and may be used in subsequent requests to access the snapshot.
+ SnapshotDateTime string
+}
+
+// Snapshot captures a Snapshot of a given Blob
+func (client Client) Snapshot(ctx context.Context, accountName, containerName, blobName string, input SnapshotInput) (result SnapshotResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "Snapshot", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "Snapshot", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "Snapshot", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "Snapshot", "`blobName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("blobs.Client", "Snapshot", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.SnapshotPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Snapshot", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SnapshotSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Snapshot", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SnapshotResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Snapshot", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SnapshotPreparer prepares the Snapshot request.
+func (client Client) SnapshotPreparer(ctx context.Context, accountName, containerName, blobName string, input SnapshotInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "snapshot"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ if input.IfModifiedSince != nil {
+ headers["If-Modified-Since"] = *input.IfModifiedSince
+ }
+ if input.IfUnmodifiedSince != nil {
+ headers["If-Unmodified-Since"] = *input.IfUnmodifiedSince
+ }
+ if input.IfMatch != nil {
+ headers["If-Match"] = *input.IfMatch
+ }
+ if input.IfNoneMatch != nil {
+ headers["If-None-Match"] = *input.IfNoneMatch
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SnapshotSender sends the Snapshot request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SnapshotSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SnapshotResponder handles the response to the Snapshot request. The method always
+// closes the http.Response Body.
+func (client Client) SnapshotResponder(resp *http.Response) (result SnapshotResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.ETag = resp.Header.Get("ETag")
+ result.SnapshotDateTime = resp.Header.Get("x-ms-snapshot")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/snapshot_get_properties.go b/storage/2017-07-29/blob/blobs/snapshot_get_properties.go
new file mode 100644
index 0000000..fe1be63
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/snapshot_get_properties.go
@@ -0,0 +1,90 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetSnapshotPropertiesInput struct {
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+
+ // The ID of the Snapshot which should be retrieved
+ SnapshotID string
+}
+
+// GetSnapshotProperties returns all user-defined metadata, standard HTTP properties, and system properties for
+// the specified snapshot of a blob
+func (client Client) GetSnapshotProperties(ctx context.Context, accountName, containerName, blobName string, input GetSnapshotPropertiesInput) (result GetPropertiesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "GetSnapshotProperties", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "GetSnapshotProperties", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "GetSnapshotProperties", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "GetSnapshotProperties", "`blobName` cannot be an empty string.")
+ }
+ if input.SnapshotID == "" {
+ return result, validation.NewError("blobs.Client", "GetSnapshotProperties", "`input.SnapshotID` cannot be an empty string.")
+ }
+
+ req, err := client.GetSnapshotPropertiesPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetSnapshotProperties", nil, "Failure preparing request")
+ return
+ }
+
+ // we re-use the GetProperties methods since this is otherwise the same
+ resp, err := client.GetPropertiesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetSnapshotProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetSnapshotProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetSnapshotPreparer prepares the GetSnapshot request.
+func (client Client) GetSnapshotPropertiesPreparer(ctx context.Context, accountName, containerName, blobName string, input GetSnapshotPropertiesInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "snapshot": autorest.Encode("query", input.SnapshotID),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsHead(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
diff --git a/storage/2017-07-29/blob/blobs/snapshot_test.go b/storage/2017-07-29/blob/blobs/snapshot_test.go
new file mode 100644
index 0000000..efc26e8
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/snapshot_test.go
@@ -0,0 +1,159 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "testing"
+ "time"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/blob/containers"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestSnapshotLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "example.txt"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithAuthorizer(containersClient.Client, storageAuth)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatalf("Error creating: %s", err)
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Copying file to Blob Storage..")
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+
+ refreshInterval := 5 * time.Second
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, fileName, copyInput, refreshInterval); err != nil {
+ t.Fatalf("Error copying: %s", err)
+ }
+
+ t.Logf("[DEBUG] First Snapshot..")
+ firstSnapshot, err := blobClient.Snapshot(ctx, accountName, containerName, fileName, SnapshotInput{})
+ if err != nil {
+ t.Fatalf("Error taking first snapshot: %s", err)
+ }
+ t.Logf("[DEBUG] First Snapshot ID: %q", firstSnapshot.SnapshotDateTime)
+
+ t.Log("[DEBUG] Waiting 2 seconds..")
+ time.Sleep(2 * time.Second)
+
+ t.Logf("[DEBUG] Second Snapshot..")
+ secondSnapshot, err := blobClient.Snapshot(ctx, accountName, containerName, fileName, SnapshotInput{
+ MetaData: map[string]string{
+ "hello": "world",
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error taking Second snapshot: %s", err)
+ }
+ t.Logf("[DEBUG] Second Snapshot ID: %q", secondSnapshot.SnapshotDateTime)
+
+ t.Logf("[DEBUG] Leasing the Blob..")
+ leaseDetails, err := blobClient.AcquireLease(ctx, accountName, containerName, fileName, AcquireLeaseInput{
+ // infinite
+ LeaseDuration: -1,
+ })
+ if err != nil {
+ t.Fatalf("Error leasing Blob: %s", err)
+ }
+ t.Logf("[DEBUG] Lease ID: %q", leaseDetails.LeaseID)
+
+ t.Logf("[DEBUG] Third Snapshot..")
+ thirdSnapshot, err := blobClient.Snapshot(ctx, accountName, containerName, fileName, SnapshotInput{
+ LeaseID: &leaseDetails.LeaseID,
+ })
+ if err != nil {
+ t.Fatalf("Error taking Third snapshot: %s", err)
+ }
+ t.Logf("[DEBUG] Third Snapshot ID: %q", thirdSnapshot.SnapshotDateTime)
+
+ t.Logf("[DEBUG] Releasing Lease..")
+ if _, err := blobClient.ReleaseLease(ctx, accountName, containerName, fileName, leaseDetails.LeaseID); err != nil {
+ t.Fatalf("Error releasing Lease: %s", err)
+ }
+
+ // get the properties from the blob, which should include the LastModifiedDate
+ t.Logf("[DEBUG] Retrieving Properties for Blob")
+ props, err := blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error getting properties: %s", err)
+ }
+
+ // confirm that the If-Modified-None returns an error
+ t.Logf("[DEBUG] Third Snapshot..")
+ fourthSnapshot, err := blobClient.Snapshot(ctx, accountName, containerName, fileName, SnapshotInput{
+ LeaseID: &leaseDetails.LeaseID,
+ IfModifiedSince: &props.LastModified,
+ })
+ if err == nil {
+ t.Fatalf("Expected an error but didn't get one")
+ }
+ if fourthSnapshot.Response.StatusCode != http.StatusPreconditionFailed {
+ t.Fatalf("Expected the status code to be Precondition Failed but got: %d", fourthSnapshot.Response.StatusCode)
+ }
+
+ t.Logf("[DEBUG] Retrieving the Second Snapshot Properties..")
+ getSecondSnapshotInput := GetSnapshotPropertiesInput{
+ SnapshotID: secondSnapshot.SnapshotDateTime,
+ }
+ if _, err := blobClient.GetSnapshotProperties(ctx, accountName, containerName, fileName, getSecondSnapshotInput); err != nil {
+ t.Fatalf("Error retrieving properties for the second snapshot: %s", err)
+ }
+
+ t.Logf("[DEBUG] Deleting the Second Snapshot..")
+ deleteSnapshotInput := DeleteSnapshotInput{
+ SnapshotDateTime: secondSnapshot.SnapshotDateTime,
+ }
+ if _, err := blobClient.DeleteSnapshot(ctx, accountName, containerName, fileName, deleteSnapshotInput); err != nil {
+ t.Fatalf("Error deleting snapshot: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-Retrieving the Second Snapshot Properties..")
+ secondSnapshotProps, err := blobClient.GetSnapshotProperties(ctx, accountName, containerName, fileName, getSecondSnapshotInput)
+ if err == nil {
+ t.Fatalf("Expected an error retrieving the snapshot but got none")
+ }
+ if secondSnapshotProps.Response.StatusCode != http.StatusNotFound {
+ t.Fatalf("Expected the status code to be %d but got %q", http.StatusNoContent, secondSnapshotProps.Response.StatusCode)
+ }
+
+ t.Logf("[DEBUG] Deleting all the snapshots..")
+ if _, err := blobClient.DeleteSnapshots(ctx, accountName, containerName, fileName, DeleteSnapshotsInput{}); err != nil {
+ t.Fatalf("Error deleting snapshots: %s", err)
+ }
+
+ t.Logf("[DEBUG] Deleting the Blob..")
+ deleteInput := DeleteInput{
+ DeleteSnapshots: false,
+ }
+ if _, err := blobClient.Delete(ctx, accountName, containerName, fileName, deleteInput); err != nil {
+ t.Fatalf("Error deleting Blob: %s", err)
+ }
+}
diff --git a/storage/2017-07-29/blob/blobs/undelete.go b/storage/2017-07-29/blob/blobs/undelete.go
new file mode 100644
index 0000000..9be2f81
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/undelete.go
@@ -0,0 +1,92 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Undelete restores the contents and metadata of soft deleted blob and any associated soft deleted snapshots.
+func (client Client) Undelete(ctx context.Context, accountName, containerName, blobName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "Undelete", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "Undelete", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "Undelete", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "Undelete", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.UndeletePreparer(ctx, accountName, containerName, blobName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Undelete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.UndeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Undelete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.UndeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Undelete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// UndeletePreparer prepares the Undelete request.
+func (client Client) UndeletePreparer(ctx context.Context, accountName, containerName, blobName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "undelete"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// UndeleteSender sends the Undelete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) UndeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// UndeleteResponder handles the response to the Undelete request. The method always
+// closes the http.Response Body.
+func (client Client) UndeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2017-07-29/blob/blobs/version.go b/storage/2017-07-29/blob/blobs/version.go
new file mode 100644
index 0000000..69bbed2
--- /dev/null
+++ b/storage/2017-07-29/blob/blobs/version.go
@@ -0,0 +1,14 @@
+package blobs
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2017-07-29"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2017-07-29/blob/containers/README.md b/storage/2017-07-29/blob/containers/README.md
new file mode 100644
index 0000000..cd6b1bf
--- /dev/null
+++ b/storage/2017-07-29/blob/containers/README.md
@@ -0,0 +1,44 @@
+## Blob Storage Container SDK for API version 2017-07-29
+
+This package allows you to interact with the Containers Blob Storage API
+
+### Supported Authorizers
+
+* SharedKeyLite (Blob, File & Queue)
+
+Note: when using the `ListBlobs` operation, only `SharedKeyLite` authentication is supported.
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/blob/containers"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ containerName := "mycontainer"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ containersClient := containers.New()
+ containersClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ createInput := containers.CreateInput{
+ AccessLevel: containers.Private,
+ }
+ if _, err := containersClient.Create(ctx, accountName, containerName, createInput); err != nil {
+ return fmt.Errorf("Error creating Container: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2017-07-29/blob/containers/client.go b/storage/2017-07-29/blob/containers/client.go
new file mode 100644
index 0000000..7bf4947
--- /dev/null
+++ b/storage/2017-07-29/blob/containers/client.go
@@ -0,0 +1,34 @@
+package containers
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for Blob Storage Containers.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithBaseURI creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
+
+func (client Client) setAccessLevelIntoHeaders(headers map[string]interface{}, level AccessLevel) map[string]interface{} {
+ // If this header is not included in the request, container data is private to the account owner.
+ if level != Private {
+ headers["x-ms-blob-public-access"] = string(level)
+ }
+
+ return headers
+}
diff --git a/storage/2017-07-29/blob/containers/create.go b/storage/2017-07-29/blob/containers/create.go
new file mode 100644
index 0000000..84c2887
--- /dev/null
+++ b/storage/2017-07-29/blob/containers/create.go
@@ -0,0 +1,123 @@
+package containers
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type CreateInput struct {
+ // Specifies whether data in the container may be accessed publicly and the level of access
+ AccessLevel AccessLevel
+
+ // A name-value pair to associate with the container as metadata.
+ MetaData map[string]string
+}
+
+type CreateResponse struct {
+ autorest.Response
+ Error *ErrorResponse `xml:"Error"`
+}
+
+// Create creates a new container under the specified account.
+// If the container with the same name already exists, the operation fails.
+func (client Client) Create(ctx context.Context, accountName, containerName string, input CreateInput) (result CreateResponse, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "Create", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "Create", "`containerName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("containers.Client", "Create", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.CreatePreparer(ctx, accountName, containerName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "Create", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "Create", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "Create", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreatePreparer prepares the Create request.
+func (client Client) CreatePreparer(ctx context.Context, accountName string, containerName string, input CreateInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = client.setAccessLevelIntoHeaders(headers, input.AccessLevel)
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSender sends the Create request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateResponder handles the response to the Create request. The method always
+// closes the http.Response Body.
+func (client Client) CreateResponder(resp *http.Response) (result CreateResponse, err error) {
+ successfulStatusCodes := []int{
+ http.StatusCreated,
+ }
+ if autorest.ResponseHasStatusCode(resp, successfulStatusCodes...) {
+ // when successful there's no response
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(successfulStatusCodes...),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ } else {
+ // however when there's an error the error's in the response
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(successfulStatusCodes...),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ }
+
+ return
+}
diff --git a/storage/2017-07-29/blob/containers/delete.go b/storage/2017-07-29/blob/containers/delete.go
new file mode 100644
index 0000000..3095829
--- /dev/null
+++ b/storage/2017-07-29/blob/containers/delete.go
@@ -0,0 +1,85 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete marks the specified container for deletion.
+// The container and any blobs contained within it are later deleted during garbage collection.
+func (client Client) Delete(ctx context.Context, accountName, containerName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "Delete", "`containerName` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, containerName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName string, containerName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2017-07-29/blob/containers/get_properties.go b/storage/2017-07-29/blob/containers/get_properties.go
new file mode 100644
index 0000000..1e308da
--- /dev/null
+++ b/storage/2017-07-29/blob/containers/get_properties.go
@@ -0,0 +1,124 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// GetProperties returns the properties for this Container without a Lease
+func (client Client) GetProperties(ctx context.Context, accountName, containerName string) (ContainerProperties, error) {
+ // If specified, Get Container Properties only succeeds if the container’s lease is active and matches this ID.
+ // If there is no active lease or the ID does not match, 412 (Precondition Failed) is returned.
+ return client.GetPropertiesWithLeaseID(ctx, accountName, containerName, "")
+}
+
+// GetPropertiesWithLeaseID returns the properties for this Container using the specified LeaseID
+func (client Client) GetPropertiesWithLeaseID(ctx context.Context, accountName, containerName, leaseID string) (result ContainerProperties, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "GetPropertiesWithLeaseID", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "GetPropertiesWithLeaseID", "`containerName` cannot be an empty string.")
+ }
+
+ req, err := client.GetPropertiesWithLeaseIDPreparer(ctx, accountName, containerName, leaseID)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "GetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetPropertiesWithLeaseIDSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "GetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetPropertiesWithLeaseIDResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "GetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPropertiesWithLeaseIDPreparer prepares the GetPropertiesWithLeaseID request.
+func (client Client) GetPropertiesWithLeaseIDPreparer(ctx context.Context, accountName, containerName, leaseID string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ // If specified, Get Container Properties only succeeds if the container’s lease is active and matches this ID.
+ // If there is no active lease or the ID does not match, 412 (Precondition Failed) is returned.
+ if leaseID != "" {
+ headers["x-ms-lease-id"] = leaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetPropertiesWithLeaseIDSender sends the GetPropertiesWithLeaseID request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetPropertiesWithLeaseIDSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetPropertiesWithLeaseIDResponder handles the response to the GetPropertiesWithLeaseID request. The method always
+// closes the http.Response Body.
+func (client Client) GetPropertiesWithLeaseIDResponder(resp *http.Response) (result ContainerProperties, err error) {
+ if resp != nil {
+ result.LeaseStatus = LeaseStatus(resp.Header.Get("x-ms-lease-status"))
+ result.LeaseState = LeaseState(resp.Header.Get("x-ms-lease-state"))
+ if result.LeaseStatus == Locked {
+ duration := LeaseDuration(resp.Header.Get("x-ms-lease-duration"))
+ result.LeaseDuration = &duration
+ }
+
+ // If this header is not returned in the response, the container is private to the account owner.
+ accessLevel := resp.Header.Get("x-ms-blob-public-access")
+ if accessLevel != "" {
+ result.AccessLevel = AccessLevel(accessLevel)
+ } else {
+ result.AccessLevel = Private
+ }
+
+ // we can't necessarily use strconv.ParseBool here since this could be nil (only in some API versions)
+ result.HasImmutabilityPolicy = strings.EqualFold(resp.Header.Get("x-ms-has-immutability-policy"), "true")
+ result.HasLegalHold = strings.EqualFold(resp.Header.Get("x-ms-has-legal-hold"), "true")
+
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/containers/lease_acquire.go b/storage/2017-07-29/blob/containers/lease_acquire.go
new file mode 100644
index 0000000..061c863
--- /dev/null
+++ b/storage/2017-07-29/blob/containers/lease_acquire.go
@@ -0,0 +1,115 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type AcquireLeaseInput struct {
+ // Specifies the duration of the lease, in seconds, or negative one (-1) for a lease that never expires.
+ // A non-infinite lease can be between 15 and 60 seconds
+ LeaseDuration int
+
+ ProposedLeaseID string
+}
+
+type AcquireLeaseResponse struct {
+ autorest.Response
+
+ LeaseID string
+}
+
+// AcquireLease establishes and manages a lock on a container for delete operations.
+func (client Client) AcquireLease(ctx context.Context, accountName, containerName string, input AcquireLeaseInput) (result AcquireLeaseResponse, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "AcquireLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "AcquireLease", "`containerName` cannot be an empty string.")
+ }
+ // An infinite lease duration is -1 seconds. A non-infinite lease can be between 15 and 60 seconds
+ if input.LeaseDuration != -1 && (input.LeaseDuration <= 15 || input.LeaseDuration >= 60) {
+ return result, validation.NewError("containers.Client", "AcquireLease", "`input.LeaseDuration` must be -1 (infinite), or between 15 and 60 seconds.")
+ }
+
+ req, err := client.AcquireLeasePreparer(ctx, accountName, containerName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "AcquireLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.AcquireLeaseSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "AcquireLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.AcquireLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "AcquireLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// AcquireLeasePreparer prepares the AcquireLease request.
+func (client Client) AcquireLeasePreparer(ctx context.Context, accountName string, containerName string, input AcquireLeaseInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ "comp": autorest.Encode("path", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "acquire",
+ "x-ms-lease-duration": input.LeaseDuration,
+ }
+
+ if input.ProposedLeaseID != "" {
+ headers["x-ms-proposed-lease-id"] = input.ProposedLeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// AcquireLeaseSender sends the AcquireLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) AcquireLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// AcquireLeaseResponder handles the response to the AcquireLease request. The method always
+// closes the http.Response Body.
+func (client Client) AcquireLeaseResponder(resp *http.Response) (result AcquireLeaseResponse, err error) {
+ if resp != nil {
+ result.LeaseID = resp.Header.Get("x-ms-lease-id")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/containers/lease_break.go b/storage/2017-07-29/blob/containers/lease_break.go
new file mode 100644
index 0000000..08acfb7
--- /dev/null
+++ b/storage/2017-07-29/blob/containers/lease_break.go
@@ -0,0 +1,129 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+ "strconv"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type BreakLeaseInput struct {
+ // For a break operation, proposed duration the lease should continue
+ // before it is broken, in seconds, between 0 and 60.
+ // This break period is only used if it is shorter than the time remaining on the lease.
+ // If longer, the time remaining on the lease is used.
+ // A new lease will not be available before the break period has expired,
+ // but the lease may be held for longer than the break period.
+ // If this header does not appear with a break operation, a fixed-duration lease breaks
+ // after the remaining lease period elapses, and an infinite lease breaks immediately.
+ BreakPeriod *int
+
+ LeaseID string
+}
+
+type BreakLeaseResponse struct {
+ autorest.Response
+
+ // Approximate time remaining in the lease period, in seconds.
+ // If the break is immediate, 0 is returned.
+ LeaseTime int
+}
+
+// BreakLease breaks a lock based on it's Lease ID
+func (client Client) BreakLease(ctx context.Context, accountName, containerName string, input BreakLeaseInput) (result BreakLeaseResponse, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "BreakLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "BreakLease", "`containerName` cannot be an empty string.")
+ }
+ if input.LeaseID == "" {
+ return result, validation.NewError("containers.Client", "BreakLease", "`input.LeaseID` cannot be an empty string.")
+ }
+
+ req, err := client.BreakLeasePreparer(ctx, accountName, containerName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "BreakLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.BreakLeaseSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "BreakLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.BreakLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "BreakLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// BreakLeasePreparer prepares the BreakLease request.
+func (client Client) BreakLeasePreparer(ctx context.Context, accountName string, containerName string, input BreakLeaseInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ "comp": autorest.Encode("path", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "break",
+ "x-ms-lease-id": input.LeaseID,
+ }
+
+ if input.BreakPeriod != nil {
+ headers["x-ms-lease-break-period"] = *input.BreakPeriod
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// BreakLeaseSender sends the BreakLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) BreakLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// BreakLeaseResponder handles the response to the BreakLease request. The method always
+// closes the http.Response Body.
+func (client Client) BreakLeaseResponder(resp *http.Response) (result BreakLeaseResponse, err error) {
+ if resp != nil {
+ leaseRaw := resp.Header.Get("x-ms-lease-time")
+ if leaseRaw != "" {
+ i, err := strconv.Atoi(leaseRaw)
+ if err == nil {
+ result.LeaseTime = i
+ }
+ }
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/containers/lease_change.go b/storage/2017-07-29/blob/containers/lease_change.go
new file mode 100644
index 0000000..dfbcb13
--- /dev/null
+++ b/storage/2017-07-29/blob/containers/lease_change.go
@@ -0,0 +1,111 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type ChangeLeaseInput struct {
+ ExistingLeaseID string
+ ProposedLeaseID string
+}
+
+type ChangeLeaseResponse struct {
+ autorest.Response
+
+ LeaseID string
+}
+
+// ChangeLease changes the lock from one Lease ID to another Lease ID
+func (client Client) ChangeLease(ctx context.Context, accountName, containerName string, input ChangeLeaseInput) (result ChangeLeaseResponse, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "ChangeLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "ChangeLease", "`containerName` cannot be an empty string.")
+ }
+ if input.ExistingLeaseID == "" {
+ return result, validation.NewError("containers.Client", "ChangeLease", "`input.ExistingLeaseID` cannot be an empty string.")
+ }
+ if input.ProposedLeaseID == "" {
+ return result, validation.NewError("containers.Client", "ChangeLease", "`input.ProposedLeaseID` cannot be an empty string.")
+ }
+
+ req, err := client.ChangeLeasePreparer(ctx, accountName, containerName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "ChangeLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ChangeLeaseSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "ChangeLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ChangeLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "ChangeLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ChangeLeasePreparer prepares the ChangeLease request.
+func (client Client) ChangeLeasePreparer(ctx context.Context, accountName string, containerName string, input ChangeLeaseInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ "comp": autorest.Encode("path", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "change",
+ "x-ms-lease-id": input.ExistingLeaseID,
+ "x-ms-proposed-lease-id": input.ProposedLeaseID,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ChangeLeaseSender sends the ChangeLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ChangeLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ChangeLeaseResponder handles the response to the ChangeLease request. The method always
+// closes the http.Response Body.
+func (client Client) ChangeLeaseResponder(resp *http.Response) (result ChangeLeaseResponse, err error) {
+ if resp != nil {
+ result.LeaseID = resp.Header.Get("x-ms-lease-id")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/containers/lease_release.go b/storage/2017-07-29/blob/containers/lease_release.go
new file mode 100644
index 0000000..fafcf98
--- /dev/null
+++ b/storage/2017-07-29/blob/containers/lease_release.go
@@ -0,0 +1,92 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// ReleaseLease releases the lock based on the Lease ID
+func (client Client) ReleaseLease(ctx context.Context, accountName, containerName, leaseID string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "ReleaseLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "ReleaseLease", "`containerName` cannot be an empty string.")
+ }
+ if leaseID == "" {
+ return result, validation.NewError("containers.Client", "ReleaseLease", "`leaseID` cannot be an empty string.")
+ }
+
+ req, err := client.ReleaseLeasePreparer(ctx, accountName, containerName, leaseID)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "ReleaseLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ReleaseLeaseSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "ReleaseLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ReleaseLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "ReleaseLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ReleaseLeasePreparer prepares the ReleaseLease request.
+func (client Client) ReleaseLeasePreparer(ctx context.Context, accountName string, containerName string, leaseID string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ "comp": autorest.Encode("path", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "release",
+ "x-ms-lease-id": leaseID,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ReleaseLeaseSender sends the ReleaseLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ReleaseLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ReleaseLeaseResponder handles the response to the ReleaseLease request. The method always
+// closes the http.Response Body.
+func (client Client) ReleaseLeaseResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/containers/lease_renew.go b/storage/2017-07-29/blob/containers/lease_renew.go
new file mode 100644
index 0000000..3fe1765
--- /dev/null
+++ b/storage/2017-07-29/blob/containers/lease_renew.go
@@ -0,0 +1,92 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// RenewLease renewes the lock based on the Lease ID
+func (client Client) RenewLease(ctx context.Context, accountName, containerName, leaseID string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "RenewLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "RenewLease", "`containerName` cannot be an empty string.")
+ }
+ if leaseID == "" {
+ return result, validation.NewError("containers.Client", "RenewLease", "`leaseID` cannot be an empty string.")
+ }
+
+ req, err := client.RenewLeasePreparer(ctx, accountName, containerName, leaseID)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "RenewLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.RenewLeaseSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "RenewLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.RenewLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "RenewLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// RenewLeasePreparer prepares the RenewLease request.
+func (client Client) RenewLeasePreparer(ctx context.Context, accountName string, containerName string, leaseID string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ "comp": autorest.Encode("path", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "renew",
+ "x-ms-lease-id": leaseID,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// RenewLeaseSender sends the RenewLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) RenewLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// RenewLeaseResponder handles the response to the RenewLease request. The method always
+// closes the http.Response Body.
+func (client Client) RenewLeaseResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/containers/lifecycle_test.go b/storage/2017-07-29/blob/containers/lifecycle_test.go
new file mode 100644
index 0000000..389c773
--- /dev/null
+++ b/storage/2017-07-29/blob/containers/lifecycle_test.go
@@ -0,0 +1,174 @@
+package containers
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestContainerLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ containersClient := NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithAuthorizer(containersClient.Client, storageAuth)
+
+ // first let's test an empty container
+ input := CreateInput{}
+ _, err = containersClient.Create(ctx, accountName, containerName, input)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+
+ container, err := containersClient.GetProperties(ctx, accountName, containerName)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error retrieving: %s", err))
+ }
+
+ if container.AccessLevel != Private {
+ t.Fatalf("Expected Access Level to be Private but got %q", container.AccessLevel)
+ }
+ if len(container.MetaData) != 0 {
+ t.Fatalf("Expected MetaData to be empty but got: %s", container.MetaData)
+ }
+ if container.LeaseStatus != Unlocked {
+ t.Fatalf("Expected Container Lease to be Unlocked but was: %s", container.LeaseStatus)
+ }
+
+ // then update the metadata
+ metaData := map[string]string{
+ "dont": "kill-my-vibe",
+ }
+ _, err = containersClient.SetMetaData(ctx, accountName, containerName, metaData)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error updating metadata: %s", err))
+ }
+
+ // give azure time to replicate
+ time.Sleep(2 * time.Second)
+
+ // then assert that
+ container, err = containersClient.GetProperties(ctx, accountName, containerName)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error re-retrieving: %s", err))
+ }
+ if len(container.MetaData) != 1 {
+ t.Fatalf("Expected 1 item in the metadata but got: %s", container.MetaData)
+ }
+ if container.MetaData["dont"] != "kill-my-vibe" {
+ t.Fatalf("Expected `kill-my-vibe` but got %q", container.MetaData["dont"])
+ }
+ if container.AccessLevel != Private {
+ t.Fatalf("Expected Access Level to be Private but got %q", container.AccessLevel)
+ }
+ if container.LeaseStatus != Unlocked {
+ t.Fatalf("Expected Container Lease to be Unlocked but was: %s", container.LeaseStatus)
+ }
+
+ // then update the ACL
+ _, err = containersClient.SetAccessControl(ctx, accountName, containerName, Blob)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error updating ACL's: %s", err))
+ }
+
+ // give azure some time to replicate
+ time.Sleep(2 * time.Second)
+
+ // then assert that
+ container, err = containersClient.GetProperties(ctx, accountName, containerName)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error re-retrieving: %s", err))
+ }
+ if container.AccessLevel != Blob {
+ t.Fatalf("Expected Access Level to be Blob but got %q", container.AccessLevel)
+ }
+ if len(container.MetaData) != 1 {
+ t.Fatalf("Expected 1 item in the metadata but got: %s", container.MetaData)
+ }
+ if container.LeaseStatus != Unlocked {
+ t.Fatalf("Expected Container Lease to be Unlocked but was: %s", container.LeaseStatus)
+ }
+
+ // acquire a lease for 30s
+ acquireLeaseInput := AcquireLeaseInput{
+ LeaseDuration: 30,
+ }
+ acquireLeaseResp, err := containersClient.AcquireLease(ctx, accountName, containerName, acquireLeaseInput)
+ if err != nil {
+ t.Fatalf("Error acquiring lease: %s", err)
+ }
+ t.Logf("[DEBUG] Lease ID: %s", acquireLeaseResp.LeaseID)
+
+ // we should then be able to update the ID
+ t.Logf("[DEBUG] Changing lease..")
+ updateLeaseInput := ChangeLeaseInput{
+ ExistingLeaseID: acquireLeaseResp.LeaseID,
+ ProposedLeaseID: "aaaabbbb-aaaa-bbbb-cccc-aaaabbbbcccc",
+ }
+ updateLeaseResp, err := containersClient.ChangeLease(ctx, accountName, containerName, updateLeaseInput)
+ if err != nil {
+ t.Fatalf("Error changing lease: %s", err)
+ }
+
+ // then renew it
+ _, err = containersClient.RenewLease(ctx, accountName, containerName, updateLeaseResp.LeaseID)
+ if err != nil {
+ t.Fatalf("Error renewing lease: %s", err)
+ }
+
+ // and then give it a timeout
+ breakPeriod := 20
+ breakLeaseInput := BreakLeaseInput{
+ LeaseID: updateLeaseResp.LeaseID,
+ BreakPeriod: &breakPeriod,
+ }
+ breakLeaseResp, err := containersClient.BreakLease(ctx, accountName, containerName, breakLeaseInput)
+ if err != nil {
+ t.Fatalf("Error breaking lease: %s", err)
+ }
+ if breakLeaseResp.LeaseTime == 0 {
+ t.Fatalf("Lease broke immediately when should have waited: %d", breakLeaseResp.LeaseTime)
+ }
+
+ // and finally ditch it
+ _, err = containersClient.ReleaseLease(ctx, accountName, containerName, updateLeaseResp.LeaseID)
+ if err != nil {
+ t.Fatalf("Error releasing lease: %s", err)
+ }
+
+ t.Logf("[DEBUG] Listing blobs in the container..")
+ listInput := ListBlobsInput{}
+ listResult, err := containersClient.ListBlobs(ctx, accountName, containerName, listInput)
+ if err != nil {
+ t.Fatalf("Error listing blobs: %s", err)
+ }
+
+ if len(listResult.Blobs.Blobs) != 0 {
+ t.Fatalf("Expected there to be no blobs in the container but got %d", len(listResult.Blobs.Blobs))
+ }
+
+ t.Logf("[DEBUG] Deleting..")
+ _, err = containersClient.Delete(ctx, accountName, containerName)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error deleting: %s", err))
+ }
+}
diff --git a/storage/2017-07-29/blob/containers/list_blobs.go b/storage/2017-07-29/blob/containers/list_blobs.go
new file mode 100644
index 0000000..82797d0
--- /dev/null
+++ b/storage/2017-07-29/blob/containers/list_blobs.go
@@ -0,0 +1,179 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type ListBlobsInput struct {
+ Delimiter *string
+ Include *[]Dataset
+ Marker *string
+ MaxResults *int
+ Prefix *string
+}
+
+type ListBlobsResult struct {
+ autorest.Response
+
+ Delimiter string `xml:"Delimiter"`
+ Marker string `xml:"Marker"`
+ MaxResults int `xml:"MaxResults"`
+ NextMarker *string `xml:"NextMarker,omitempty"`
+ Prefix string `xml:"Prefix"`
+ Blobs Blobs `xml:"Blobs"`
+}
+
+type Blobs struct {
+ Blobs []BlobDetails `xml:"Blob"`
+ BlobPrefix *BlobPrefix `xml:"BlobPrefix"`
+}
+
+type BlobDetails struct {
+ Name string `xml:"Name"`
+ Deleted bool `xml:"Deleted,omitempty"`
+ MetaData map[string]interface{} `map:"Metadata,omitempty"`
+ Properties *BlobProperties `xml:"Properties,omitempty"`
+ Snapshot *string `xml:"Snapshot,omitempty"`
+}
+
+type BlobProperties struct {
+ AccessTier *string `xml:"AccessTier,omitempty"`
+ AccessTierInferred *bool `xml:"AccessTierInferred,omitempty"`
+ AccessTierChangeTime *string `xml:"AccessTierChangeTime,omitempty"`
+ BlobType *string `xml:"BlobType,omitempty"`
+ BlobSequenceNumber *string `xml:"x-ms-blob-sequence-number,omitempty"`
+ CacheControl *string `xml:"Cache-Control,omitempty"`
+ ContentEncoding *string `xml:"ContentEncoding,omitempty"`
+ ContentLanguage *string `xml:"Content-Language,omitempty"`
+ ContentLength *int64 `xml:"Content-Length,omitempty"`
+ ContentMD5 *string `xml:"Content-MD5,omitempty"`
+ ContentType *string `xml:"Content-Type,omitempty"`
+ CopyCompletionTime *string `xml:"CopyCompletionTime,omitempty"`
+ CopyId *string `xml:"CopyId,omitempty"`
+ CopyStatus *string `xml:"CopyStatus,omitempty"`
+ CopySource *string `xml:"CopySource,omitempty"`
+ CopyProgress *string `xml:"CopyProgress,omitempty"`
+ CopyStatusDescription *string `xml:"CopyStatusDescription,omitempty"`
+ CreationTime *string `xml:"CreationTime,omitempty"`
+ ETag *string `xml:"Etag,omitempty"`
+ DeletedTime *string `xml:"DeletedTime,omitempty"`
+ IncrementalCopy *bool `xml:"IncrementalCopy,omitempty"`
+ LastModified *string `xml:"Last-Modified,omitempty"`
+ LeaseDuration *string `xml:"LeaseDuration,omitempty"`
+ LeaseState *string `xml:"LeaseState,omitempty"`
+ LeaseStatus *string `xml:"LeaseStatus,omitempty"`
+ RemainingRetentionDays *string `xml:"RemainingRetentionDays,omitempty"`
+ ServerEncrypted *bool `xml:"ServerEncrypted,omitempty"`
+}
+
+type BlobPrefix struct {
+ Name string `xml:"Name"`
+}
+
+// ListBlobs lists the blobs matching the specified query within the specified Container
+func (client Client) ListBlobs(ctx context.Context, accountName, containerName string, input ListBlobsInput) (result ListBlobsResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "ListBlobs", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "ListBlobs", "`containerName` cannot be an empty string.")
+ }
+ if input.MaxResults != nil && (*input.MaxResults <= 0 || *input.MaxResults > 5000) {
+ return result, validation.NewError("containers.Client", "ListBlobs", "`input.MaxResults` can either be nil or between 0 and 5000.")
+ }
+
+ req, err := client.ListBlobsPreparer(ctx, accountName, containerName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "ListBlobs", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ListBlobsSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "ListBlobs", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ListBlobsResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "ListBlobs", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ListBlobsPreparer prepares the ListBlobs request.
+func (client Client) ListBlobsPreparer(ctx context.Context, accountName, containerName string, input ListBlobsInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "list"),
+ "restype": autorest.Encode("query", "container"),
+ }
+
+ if input.Delimiter != nil {
+ queryParameters["delimiter"] = autorest.Encode("query", *input.Delimiter)
+ }
+ if input.Include != nil {
+ vals := make([]string, 0)
+ for _, v := range *input.Include {
+ vals = append(vals, string(v))
+ }
+ include := strings.Join(vals, ",")
+ queryParameters["include"] = autorest.Encode("query", include)
+ }
+ if input.Marker != nil {
+ queryParameters["marker"] = autorest.Encode("query", *input.Marker)
+ }
+ if input.MaxResults != nil {
+ queryParameters["maxresults"] = autorest.Encode("query", *input.MaxResults)
+ }
+ if input.Prefix != nil {
+ queryParameters["prefix"] = autorest.Encode("query", *input.Prefix)
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ListBlobsSender sends the ListBlobs request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ListBlobsSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ListBlobsResponder handles the response to the ListBlobs request. The method always
+// closes the http.Response Body.
+func (client Client) ListBlobsResponder(resp *http.Response) (result ListBlobsResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/containers/models.go b/storage/2017-07-29/blob/containers/models.go
new file mode 100644
index 0000000..adba368
--- /dev/null
+++ b/storage/2017-07-29/blob/containers/models.go
@@ -0,0 +1,75 @@
+package containers
+
+import "github.com/Azure/go-autorest/autorest"
+
+type AccessLevel string
+
+var (
+ // Blob specifies public read access for blobs.
+ // Blob data within this container can be read via anonymous request,
+ // but container data is not available.
+ // Clients cannot enumerate blobs within the container via anonymous request.
+ Blob AccessLevel = "blob"
+
+ // Container specifies full public read access for container and blob data.
+ // Clients can enumerate blobs within the container via anonymous request,
+ // but cannot enumerate containers within the storage account.
+ Container AccessLevel = "container"
+
+ // Private specifies that container data is private to the account owner
+ Private AccessLevel = ""
+)
+
+type ContainerProperties struct {
+ autorest.Response
+
+ AccessLevel AccessLevel
+ LeaseStatus LeaseStatus
+ LeaseState LeaseState
+ LeaseDuration *LeaseDuration
+ MetaData map[string]string
+ HasImmutabilityPolicy bool
+ HasLegalHold bool
+}
+
+type Dataset string
+
+var (
+ Copy Dataset = "copy"
+ Deleted Dataset = "deleted"
+ MetaData Dataset = "metadata"
+ Snapshots Dataset = "snapshots"
+ UncommittedBlobs Dataset = "uncommittedblobs"
+)
+
+type ErrorResponse struct {
+ Code *string `xml:"Code"`
+ Message *string `xml:"Message"`
+}
+
+type LeaseDuration string
+
+var (
+ // If this lease is for a Fixed Duration
+ Fixed LeaseDuration = "fixed"
+
+ // If this lease is for an Indefinite Duration
+ Infinite LeaseDuration = "infinite"
+)
+
+type LeaseState string
+
+var (
+ Available LeaseState = "available"
+ Breaking LeaseState = "breaking"
+ Broken LeaseState = "broken"
+ Expired LeaseState = "expired"
+ Leased LeaseState = "leased"
+)
+
+type LeaseStatus string
+
+var (
+ Locked LeaseStatus = "locked"
+ Unlocked LeaseStatus = "unlocked"
+)
diff --git a/storage/2017-07-29/blob/containers/resource_id.go b/storage/2017-07-29/blob/containers/resource_id.go
new file mode 100644
index 0000000..a5bfd6e
--- /dev/null
+++ b/storage/2017-07-29/blob/containers/resource_id.go
@@ -0,0 +1,46 @@
+package containers
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Container
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, containerName string) string {
+ domain := endpoints.GetBlobEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s", domain, containerName)
+}
+
+type ResourceID struct {
+ AccountName string
+ ContainerName string
+}
+
+// ParseResourceID parses the Resource ID and returns an object which can be used
+// to interact with the Container Resource
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://foo.blob.core.windows.net/Bar
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ containerName := strings.TrimPrefix(uri.Path, "/")
+ return &ResourceID{
+ AccountName: *accountName,
+ ContainerName: containerName,
+ }, nil
+}
diff --git a/storage/2017-07-29/blob/containers/resource_id_test.go b/storage/2017-07-29/blob/containers/resource_id_test.go
new file mode 100644
index 0000000..e27bc9d
--- /dev/null
+++ b/storage/2017-07-29/blob/containers/resource_id_test.go
@@ -0,0 +1,79 @@
+package containers
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.blob.core.chinacloudapi.cn/container1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.blob.core.cloudapi.de/container1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.blob.core.windows.net/container1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.blob.core.usgovcloudapi.net/container1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "container1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.blob.core.chinacloudapi.cn/container1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.blob.core.cloudapi.de/container1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.blob.core.windows.net/container1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.blob.core.usgovcloudapi.net/container1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected the account name to be `account1` but got %q", actual.AccountName)
+ }
+
+ if actual.ContainerName != "container1" {
+ t.Fatalf("Expected the container name to be `container1` but got %q", actual.ContainerName)
+ }
+ }
+}
diff --git a/storage/2017-07-29/blob/containers/set_acl.go b/storage/2017-07-29/blob/containers/set_acl.go
new file mode 100644
index 0000000..fcf4e10
--- /dev/null
+++ b/storage/2017-07-29/blob/containers/set_acl.go
@@ -0,0 +1,100 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// SetAccessControl sets the Access Control for a Container without a Lease ID
+func (client Client) SetAccessControl(ctx context.Context, accountName, containerName string, level AccessLevel) (autorest.Response, error) {
+ return client.SetAccessControlWithLeaseID(ctx, accountName, containerName, "", level)
+}
+
+// SetAccessControlWithLeaseID sets the Access Control for a Container using the specified Lease ID
+func (client Client) SetAccessControlWithLeaseID(ctx context.Context, accountName, containerName, leaseID string, level AccessLevel) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "SetAccessControl", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "SetAccessControl", "`containerName` cannot be an empty string.")
+ }
+
+ req, err := client.SetAccessControlWithLeaseIDPreparer(ctx, accountName, containerName, leaseID, level)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "SetAccessControl", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetAccessControlWithLeaseIDSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "SetAccessControl", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetAccessControlWithLeaseIDResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "SetAccessControl", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetAccessControlWithLeaseIDPreparer prepares the SetAccessControlWithLeaseID request.
+func (client Client) SetAccessControlWithLeaseIDPreparer(ctx context.Context, accountName, containerName, leaseID string, level AccessLevel) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "acl"),
+ "restype": autorest.Encode("path", "container"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = client.setAccessLevelIntoHeaders(headers, level)
+
+ // If specified, Get Container Properties only succeeds if the container’s lease is active and matches this ID.
+ // If there is no active lease or the ID does not match, 412 (Precondition Failed) is returned.
+ if leaseID != "" {
+ headers["x-ms-lease-id"] = leaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetAccessControlWithLeaseIDSender sends the SetAccessControlWithLeaseID request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetAccessControlWithLeaseIDSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetAccessControlWithLeaseIDResponder handles the response to the SetAccessControlWithLeaseID request. The method always
+// closes the http.Response Body.
+func (client Client) SetAccessControlWithLeaseIDResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/containers/set_metadata.go b/storage/2017-07-29/blob/containers/set_metadata.go
new file mode 100644
index 0000000..fb9e07f
--- /dev/null
+++ b/storage/2017-07-29/blob/containers/set_metadata.go
@@ -0,0 +1,105 @@
+package containers
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// SetMetaData sets the specified MetaData on the Container without a Lease ID
+func (client Client) SetMetaData(ctx context.Context, accountName, containerName string, metaData map[string]string) (autorest.Response, error) {
+ return client.SetMetaDataWithLeaseID(ctx, accountName, containerName, "", metaData)
+}
+
+// SetMetaDataWithLeaseID sets the specified MetaData on the Container using the specified Lease ID
+func (client Client) SetMetaDataWithLeaseID(ctx context.Context, accountName, containerName, leaseID string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "SetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "SetMetaData", "`containerName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("containers.Client", "SetMetaData", fmt.Sprintf("`metaData` is not valid: %s.", err))
+ }
+
+ req, err := client.SetMetaDataWithLeaseIDPreparer(ctx, accountName, containerName, leaseID, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "SetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetMetaDataWithLeaseIDSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "SetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetMetaDataWithLeaseIDResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "SetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetMetaDataWithLeaseIDPreparer prepares the SetMetaDataWithLeaseID request.
+func (client Client) SetMetaDataWithLeaseIDPreparer(ctx context.Context, accountName, containerName, leaseID string, metaData map[string]string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "metadata"),
+ "restype": autorest.Encode("path", "container"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ // If specified, Get Container Properties only succeeds if the container’s lease is active and matches this ID.
+ // If there is no active lease or the ID does not match, 412 (Precondition Failed) is returned.
+ if leaseID != "" {
+ headers["x-ms-lease-id"] = leaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetMetaDataWithLeaseIDSender sends the SetMetaDataWithLeaseID request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetMetaDataWithLeaseIDSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetMetaDataWithLeaseIDResponder handles the response to the SetMetaDataWithLeaseID request. The method always
+// closes the http.Response Body.
+func (client Client) SetMetaDataWithLeaseIDResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/blob/containers/version.go b/storage/2017-07-29/blob/containers/version.go
new file mode 100644
index 0000000..5fd1a4a
--- /dev/null
+++ b/storage/2017-07-29/blob/containers/version.go
@@ -0,0 +1,14 @@
+package containers
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2017-07-29"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2017-07-29/file/directories/README.md b/storage/2017-07-29/file/directories/README.md
new file mode 100644
index 0000000..07ccafd
--- /dev/null
+++ b/storage/2017-07-29/file/directories/README.md
@@ -0,0 +1,43 @@
+## File Storage Directories SDK for API version 2017-07-29
+
+This package allows you to interact with the Directories File Storage API
+
+### Supported Authorizers
+
+* SharedKeyLite (Blob, File & Queue)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/file/directories"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ shareName := "myshare"
+ directoryName := "myfiles"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ directoriesClient := directories.New()
+ directoriesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ metadata := map[string]string{
+ "hello": "world",
+ }
+ if _, err := directoriesClient.Create(ctx, accountName, shareName, directoryName, metadata); err != nil {
+ return fmt.Errorf("Error creating Directory: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2017-07-29/file/directories/client.go b/storage/2017-07-29/file/directories/client.go
new file mode 100644
index 0000000..bf2d315
--- /dev/null
+++ b/storage/2017-07-29/file/directories/client.go
@@ -0,0 +1,25 @@
+package directories
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for File Storage Shares.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2017-07-29/file/directories/create.go b/storage/2017-07-29/file/directories/create.go
new file mode 100644
index 0000000..93f5c82
--- /dev/null
+++ b/storage/2017-07-29/file/directories/create.go
@@ -0,0 +1,101 @@
+package directories
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// Create creates a new directory under the specified share or parent directory.
+func (client Client) Create(ctx context.Context, accountName, shareName, path string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("directories.Client", "Create", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("directories.Client", "Create", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("directories.Client", "Create", "`shareName` must be a lower-cased string.")
+ }
+ if path == "" {
+ return result, validation.NewError("directories.Client", "Create", "`path` cannot be an empty string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("directories.Client", "Create", fmt.Sprintf("`metadata` is not valid: %s.", err))
+ }
+
+ req, err := client.CreatePreparer(ctx, accountName, shareName, path, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "Create", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "directories.Client", "Create", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "Create", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreatePreparer prepares the Create request.
+func (client Client) CreatePreparer(ctx context.Context, accountName, shareName, path string, metaData map[string]string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "directory"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSender sends the Create request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateResponder handles the response to the Create request. The method always
+// closes the http.Response Body.
+func (client Client) CreateResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/directories/delete.go b/storage/2017-07-29/file/directories/delete.go
new file mode 100644
index 0000000..9443c25
--- /dev/null
+++ b/storage/2017-07-29/file/directories/delete.go
@@ -0,0 +1,95 @@
+package directories
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete removes the specified empty directory
+// Note that the directory must be empty before it can be deleted.
+func (client Client) Delete(ctx context.Context, accountName, shareName, path string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("directories.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("directories.Client", "Delete", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("directories.Client", "Delete", "`shareName` must be a lower-cased string.")
+ }
+ if path == "" {
+ return result, validation.NewError("directories.Client", "Delete", "`path` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, shareName, path)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "directories.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, shareName, path string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "directory"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/directories/get.go b/storage/2017-07-29/file/directories/get.go
new file mode 100644
index 0000000..817d680
--- /dev/null
+++ b/storage/2017-07-29/file/directories/get.go
@@ -0,0 +1,112 @@
+package directories
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetResult struct {
+ autorest.Response
+
+ // A set of name-value pairs that contain metadata for the directory.
+ MetaData map[string]string
+
+ // The value of this header is set to true if the directory metadata is completely
+ // encrypted using the specified algorithm. Otherwise, the value is set to false.
+ DirectoryMetaDataEncrypted bool
+}
+
+// Get returns all system properties for the specified directory,
+// and can also be used to check the existence of a directory.
+func (client Client) Get(ctx context.Context, accountName, shareName, path string) (result GetResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("directories.Client", "Get", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("directories.Client", "Get", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("directories.Client", "Get", "`shareName` must be a lower-cased string.")
+ }
+ if path == "" {
+ return result, validation.NewError("directories.Client", "Get", "`path` cannot be an empty string.")
+ }
+
+ req, err := client.GetPreparer(ctx, accountName, shareName, path)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "Get", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "directories.Client", "Get", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "Get", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPreparer prepares the Get request.
+func (client Client) GetPreparer(ctx context.Context, accountName, shareName, path string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "directory"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetSender sends the Get request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetResponder handles the response to the Get request. The method always
+// closes the http.Response Body.
+func (client Client) GetResponder(resp *http.Response) (result GetResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ result.DirectoryMetaDataEncrypted = strings.EqualFold(resp.Header.Get("x-ms-server-encrypted"), "true")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/directories/lifecycle_test.go b/storage/2017-07-29/file/directories/lifecycle_test.go
new file mode 100644
index 0000000..855ddf7
--- /dev/null
+++ b/storage/2017-07-29/file/directories/lifecycle_test.go
@@ -0,0 +1,107 @@
+package directories
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/file/shares"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestDirectoriesLifeCycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := shares.NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ directoriesClient := NewWithEnvironment(client.Environment)
+ directoriesClient.Client = client.PrepareWithAuthorizer(directoriesClient.Client, storageAuth)
+
+ input := shares.CreateInput{
+ QuotaInGB: 1,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+ defer sharesClient.Delete(ctx, accountName, shareName, true)
+
+ metaData := map[string]string{
+ "hello": "world",
+ }
+
+ log.Printf("[DEBUG] Creating Top Level..")
+ if _, err := directoriesClient.Create(ctx, accountName, shareName, "hello", metaData); err != nil {
+ t.Fatalf("Error creating Top Level Directory: %s", err)
+ }
+
+ log.Printf("[DEBUG] Creating Inner..")
+ if _, err := directoriesClient.Create(ctx, accountName, shareName, "hello/there", metaData); err != nil {
+ t.Fatalf("Error creating Inner Directory: %s", err)
+ }
+
+ log.Printf("[DEBUG] Retrieving share")
+ innerDir, err := directoriesClient.Get(ctx, accountName, shareName, "hello/there")
+ if err != nil {
+ t.Fatalf("Error retrieving Inner Directory: %s", err)
+ }
+
+ if innerDir.DirectoryMetaDataEncrypted != true {
+ t.Fatalf("Expected MetaData to be encrypted but got: %t", innerDir.DirectoryMetaDataEncrypted)
+ }
+
+ if len(innerDir.MetaData) != 1 {
+ t.Fatalf("Expected MetaData to contain 1 item but got %d", len(innerDir.MetaData))
+ }
+ if innerDir.MetaData["hello"] != "world" {
+ t.Fatalf("Expected MetaData `hello` to be `world`: %s", innerDir.MetaData["hello"])
+ }
+
+ log.Printf("[DEBUG] Setting MetaData")
+ updatedMetaData := map[string]string{
+ "panda": "pops",
+ }
+ if _, err := directoriesClient.SetMetaData(ctx, accountName, shareName, "hello/there", updatedMetaData); err != nil {
+ t.Fatalf("Error updating MetaData: %s", err)
+ }
+
+ log.Printf("[DEBUG] Retrieving MetaData")
+ retrievedMetaData, err := directoriesClient.GetMetaData(ctx, accountName, shareName, "hello/there")
+ if err != nil {
+ t.Fatalf("Error retrieving the updated metadata: %s", err)
+ }
+ if len(retrievedMetaData.MetaData) != 1 {
+ t.Fatalf("Expected the updated metadata to have 1 item but got %d", len(retrievedMetaData.MetaData))
+ }
+ if retrievedMetaData.MetaData["panda"] != "pops" {
+ t.Fatalf("Expected the metadata `panda` to be `pops` but got %q", retrievedMetaData.MetaData["panda"])
+ }
+
+ t.Logf("[DEBUG] Deleting Inner..")
+ if _, err := directoriesClient.Delete(ctx, accountName, shareName, "hello/there"); err != nil {
+ t.Fatalf("Error deleting Inner Directory: %s", err)
+ }
+
+ t.Logf("[DEBUG] Deleting Top Level..")
+ if _, err := directoriesClient.Delete(ctx, accountName, shareName, "hello"); err != nil {
+ t.Fatalf("Error deleting Top Level Directory: %s", err)
+ }
+}
diff --git a/storage/2017-07-29/file/directories/metadata_get.go b/storage/2017-07-29/file/directories/metadata_get.go
new file mode 100644
index 0000000..173716d
--- /dev/null
+++ b/storage/2017-07-29/file/directories/metadata_get.go
@@ -0,0 +1,106 @@
+package directories
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetMetaDataResult struct {
+ autorest.Response
+
+ MetaData map[string]string
+}
+
+// GetMetaData returns all user-defined metadata for the specified directory
+func (client Client) GetMetaData(ctx context.Context, accountName, shareName, path string) (result GetMetaDataResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("directories.Client", "GetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("directories.Client", "GetMetaData", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("directories.Client", "GetMetaData", "`shareName` must be a lower-cased string.")
+ }
+ if path == "" {
+ return result, validation.NewError("directories.Client", "GetMetaData", "`path` cannot be an empty string.")
+ }
+
+ req, err := client.GetMetaDataPreparer(ctx, accountName, shareName, path)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "GetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetMetaDataSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "directories.Client", "GetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "GetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetMetaDataPreparer prepares the GetMetaData request.
+func (client Client) GetMetaDataPreparer(ctx context.Context, accountName, shareName, path string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "directory"),
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetMetaDataSender sends the GetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetMetaDataResponder handles the response to the GetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) GetMetaDataResponder(resp *http.Response) (result GetMetaDataResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/directories/metadata_set.go b/storage/2017-07-29/file/directories/metadata_set.go
new file mode 100644
index 0000000..cb13312
--- /dev/null
+++ b/storage/2017-07-29/file/directories/metadata_set.go
@@ -0,0 +1,102 @@
+package directories
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// SetMetaData updates user defined metadata for the specified directory
+func (client Client) SetMetaData(ctx context.Context, accountName, shareName, path string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("directories.Client", "SetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("directories.Client", "SetMetaData", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("directories.Client", "SetMetaData", "`shareName` must be a lower-cased string.")
+ }
+ if path == "" {
+ return result, validation.NewError("directories.Client", "SetMetaData", "`path` cannot be an empty string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("directories.Client", "SetMetaData", fmt.Sprintf("`metaData` is not valid: %s.", err))
+ }
+
+ req, err := client.SetMetaDataPreparer(ctx, accountName, shareName, path, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "SetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetMetaDataSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "directories.Client", "SetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "SetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetMetaDataPreparer prepares the SetMetaData request.
+func (client Client) SetMetaDataPreparer(ctx context.Context, accountName, shareName, path string, metaData map[string]string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "directory"),
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetMetaDataSender sends the SetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetMetaDataResponder handles the response to the SetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) SetMetaDataResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/directories/resource_id.go b/storage/2017-07-29/file/directories/resource_id.go
new file mode 100644
index 0000000..44607c4
--- /dev/null
+++ b/storage/2017-07-29/file/directories/resource_id.go
@@ -0,0 +1,56 @@
+package directories
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Directory
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, shareName, directoryName string) string {
+ domain := endpoints.GetFileEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s/%s", domain, shareName, directoryName)
+}
+
+type ResourceID struct {
+ AccountName string
+ DirectoryName string
+ ShareName string
+}
+
+// ParseResourceID parses the Resource ID into an Object
+// which can be used to interact with the Directory within the File Share
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://foo.file.core.windows.net/Bar/Folder
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ path := strings.TrimPrefix(uri.Path, "/")
+ segments := strings.Split(path, "/")
+ if len(segments) == 0 {
+ return nil, fmt.Errorf("Expected the path to contain segments but got none")
+ }
+
+ shareName := segments[0]
+ directoryName := strings.TrimPrefix(path, shareName)
+ directoryName = strings.TrimPrefix(directoryName, "/")
+ return &ResourceID{
+ AccountName: *accountName,
+ ShareName: shareName,
+ DirectoryName: directoryName,
+ }, nil
+}
diff --git a/storage/2017-07-29/file/directories/resource_id_test.go b/storage/2017-07-29/file/directories/resource_id_test.go
new file mode 100644
index 0000000..0be800d
--- /dev/null
+++ b/storage/2017-07-29/file/directories/resource_id_test.go
@@ -0,0 +1,81 @@
+package directories
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.file.core.chinacloudapi.cn/share1/directory1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.file.core.cloudapi.de/share1/directory1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.file.core.windows.net/share1/directory1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.file.core.usgovcloudapi.net/share1/directory1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "share1", "directory1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.file.core.chinacloudapi.cn/share1/directory1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.file.core.cloudapi.de/share1/directory1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.file.core.windows.net/share1/directory1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.file.core.usgovcloudapi.net/share1/directory1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.ShareName != "share1" {
+ t.Fatalf("Expected Share Name to be `share1` but got %q", actual.ShareName)
+ }
+ if actual.DirectoryName != "directory1" {
+ t.Fatalf("Expected Directory Name to be `directory1` but got %q", actual.DirectoryName)
+ }
+ }
+}
diff --git a/storage/2017-07-29/file/directories/version.go b/storage/2017-07-29/file/directories/version.go
new file mode 100644
index 0000000..ae46ef5
--- /dev/null
+++ b/storage/2017-07-29/file/directories/version.go
@@ -0,0 +1,14 @@
+package directories
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2017-07-29"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2017-07-29/file/files/README.md b/storage/2017-07-29/file/files/README.md
new file mode 100644
index 0000000..fe13669
--- /dev/null
+++ b/storage/2017-07-29/file/files/README.md
@@ -0,0 +1,42 @@
+## File Storage Files SDK for API version 2017-07-29
+
+This package allows you to interact with the Files File Storage API
+
+### Supported Authorizers
+
+* SharedKeyLite (Blob, File & Queue)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/file/files"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ shareName := "myshare"
+ directoryName := "myfiles"
+ fileName := "example.txt"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ filesClient := files.New()
+ filesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ input := files.CreateInput{}
+ if _, err := filesClient.Create(ctx, accountName, shareName, directoryName, fileName, input); err != nil {
+ return fmt.Errorf("Error creating File: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2017-07-29/file/files/client.go b/storage/2017-07-29/file/files/client.go
new file mode 100644
index 0000000..ecca815
--- /dev/null
+++ b/storage/2017-07-29/file/files/client.go
@@ -0,0 +1,25 @@
+package files
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for File Storage Shares.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2017-07-29/file/files/copy.go b/storage/2017-07-29/file/files/copy.go
new file mode 100644
index 0000000..31768b3
--- /dev/null
+++ b/storage/2017-07-29/file/files/copy.go
@@ -0,0 +1,132 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type CopyInput struct {
+ // Specifies the URL of the source file or blob, up to 2 KB in length.
+ //
+ // To copy a file to another file within the same storage account, you may use Shared Key to authenticate
+ // the source file. If you are copying a file from another storage account, or if you are copying a blob from
+ // the same storage account or another storage account, then you must authenticate the source file or blob using a
+ // shared access signature. If the source is a public blob, no authentication is required to perform the copy
+ // operation. A file in a share snapshot can also be specified as a copy source.
+ CopySource string
+
+ MetaData map[string]string
+}
+
+type CopyResult struct {
+ autorest.Response
+
+ // The CopyID, which can be passed to AbortCopy to abort the copy.
+ CopyID string
+
+ // Either `success` or `pending`
+ CopySuccess string
+}
+
+// Copy copies a blob or file to a destination file within the storage account asynchronously.
+func (client Client) Copy(ctx context.Context, accountName, shareName, path, fileName string, input CopyInput) (result CopyResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "Copy", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "Copy", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "Copy", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "Copy", "`fileName` cannot be an empty string.")
+ }
+ if input.CopySource == "" {
+ return result, validation.NewError("files.Client", "Copy", "`input.CopySource` cannot be an empty string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("files.Client", "Copy", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.CopyPreparer(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "Copy", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CopySender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "Copy", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CopyResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "Copy", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CopyPreparer prepares the Copy request.
+func (client Client) CopyPreparer(ctx context.Context, accountName, shareName, path, fileName string, input CopyInput) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-copy-source": input.CopySource,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CopySender sends the Copy request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CopySender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CopyResponder handles the response to the Copy request. The method always
+// closes the http.Response Body.
+func (client Client) CopyResponder(resp *http.Response) (result CopyResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.CopyID = resp.Header.Get("x-ms-copy-id")
+ result.CopySuccess = resp.Header.Get("x-ms-copy-status")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/files/copy_abort.go b/storage/2017-07-29/file/files/copy_abort.go
new file mode 100644
index 0000000..2f09131
--- /dev/null
+++ b/storage/2017-07-29/file/files/copy_abort.go
@@ -0,0 +1,104 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// AbortCopy aborts a pending Copy File operation, and leaves a destination file with zero length and full metadata
+func (client Client) AbortCopy(ctx context.Context, accountName, shareName, path, fileName, copyID string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "AbortCopy", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "AbortCopy", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "AbortCopy", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "AbortCopy", "`fileName` cannot be an empty string.")
+ }
+ if copyID == "" {
+ return result, validation.NewError("files.Client", "AbortCopy", "`copyID` cannot be an empty string.")
+ }
+
+ req, err := client.AbortCopyPreparer(ctx, accountName, shareName, path, fileName, copyID)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "AbortCopy", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.AbortCopySender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "AbortCopy", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.AbortCopyResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "AbortCopy", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// AbortCopyPreparer prepares the AbortCopy request.
+func (client Client) AbortCopyPreparer(ctx context.Context, accountName, shareName, path, fileName, copyID string) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "copy"),
+ "copyid": autorest.Encode("query", copyID),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-copy-action": "abort",
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// AbortCopySender sends the AbortCopy request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) AbortCopySender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// AbortCopyResponder handles the response to the AbortCopy request. The method always
+// closes the http.Response Body.
+func (client Client) AbortCopyResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/files/copy_wait.go b/storage/2017-07-29/file/files/copy_wait.go
new file mode 100644
index 0000000..e6a646b
--- /dev/null
+++ b/storage/2017-07-29/file/files/copy_wait.go
@@ -0,0 +1,55 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+)
+
+type CopyAndWaitResult struct {
+ autorest.Response
+
+ CopyID string
+}
+
+const DefaultCopyPollDuration = 15 * time.Second
+
+// CopyAndWait is a convenience method which doesn't exist in the API, which copies the file and then waits for the copy to complete
+func (client Client) CopyAndWait(ctx context.Context, accountName, shareName, path, fileName string, input CopyInput, pollDuration time.Duration) (result CopyResult, err error) {
+ copy, e := client.Copy(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ result.Response = copy.Response
+ err = fmt.Errorf("Error copying: %s", e)
+ return
+ }
+
+ result.CopyID = copy.CopyID
+
+ // since the API doesn't return a LRO, this is a hack which also polls every 10s, but should be sufficient
+ for true {
+ props, e := client.GetProperties(ctx, accountName, shareName, path, fileName)
+ if e != nil {
+ result.Response = copy.Response
+ err = fmt.Errorf("Error waiting for copy: %s", e)
+ return
+ }
+
+ switch strings.ToLower(props.CopyStatus) {
+ case "pending":
+ time.Sleep(pollDuration)
+ continue
+
+ case "success":
+ return
+
+ default:
+ err = fmt.Errorf("Unexpected CopyState %q", e)
+ return
+ }
+ }
+
+ return
+}
diff --git a/storage/2017-07-29/file/files/copy_wait_test.go b/storage/2017-07-29/file/files/copy_wait_test.go
new file mode 100644
index 0000000..bd67929
--- /dev/null
+++ b/storage/2017-07-29/file/files/copy_wait_test.go
@@ -0,0 +1,129 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/file/shares"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestFilesCopyAndWaitFromURL(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := shares.NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ input := shares.CreateInput{
+ QuotaInGB: 10,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+ defer sharesClient.Delete(ctx, accountName, shareName, false)
+
+ filesClient := NewWithEnvironment(client.Environment)
+ filesClient.Client = client.PrepareWithAuthorizer(filesClient.Client, storageAuth)
+
+ copiedFileName := "ubuntu.iso"
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+
+ t.Logf("[DEBUG] Copy And Waiting..")
+ if _, err := filesClient.CopyAndWait(ctx, accountName, shareName, "", copiedFileName, copyInput, DefaultCopyPollDuration); err != nil {
+ t.Fatalf("Error copy & waiting: %s", err)
+ }
+
+ t.Logf("[DEBUG] Asserting that the file's ready..")
+
+ props, err := filesClient.GetProperties(ctx, accountName, shareName, "", copiedFileName)
+ if err != nil {
+ t.Fatalf("Error retrieving file: %s", err)
+ }
+
+ if !strings.EqualFold(props.CopyStatus, "success") {
+ t.Fatalf("Expected the Copy Status to be `Success` but got %q", props.CopyStatus)
+ }
+}
+
+func TestFilesCopyAndWaitFromBlob(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := shares.NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ input := shares.CreateInput{
+ QuotaInGB: 10,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+ defer sharesClient.Delete(ctx, accountName, shareName, false)
+
+ filesClient := NewWithEnvironment(client.Environment)
+ filesClient.Client = client.PrepareWithAuthorizer(filesClient.Client, storageAuth)
+
+ originalFileName := "ubuntu.iso"
+ copiedFileName := "ubuntu-copied.iso"
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+ t.Logf("[DEBUG] Copy And Waiting the original file..")
+ if _, err := filesClient.CopyAndWait(ctx, accountName, shareName, "", originalFileName, copyInput, DefaultCopyPollDuration); err != nil {
+ t.Fatalf("Error copy & waiting: %s", err)
+ }
+
+ t.Logf("[DEBUG] Now copying that blob..")
+ duplicateInput := CopyInput{
+ CopySource: fmt.Sprintf("%s/%s/%s", endpoints.GetFileEndpoint(filesClient.BaseURI, accountName), shareName, originalFileName),
+ }
+ if _, err := filesClient.CopyAndWait(ctx, accountName, shareName, "", copiedFileName, duplicateInput, DefaultCopyPollDuration); err != nil {
+ t.Fatalf("Error copying duplicate: %s", err)
+ }
+
+ t.Logf("[DEBUG] Asserting that the file's ready..")
+ props, err := filesClient.GetProperties(ctx, accountName, shareName, "", copiedFileName)
+ if err != nil {
+ t.Fatalf("Error retrieving file: %s", err)
+ }
+
+ if !strings.EqualFold(props.CopyStatus, "success") {
+ t.Fatalf("Expected the Copy Status to be `Success` but got %q", props.CopyStatus)
+ }
+}
diff --git a/storage/2017-07-29/file/files/create.go b/storage/2017-07-29/file/files/create.go
new file mode 100644
index 0000000..85d4b0b
--- /dev/null
+++ b/storage/2017-07-29/file/files/create.go
@@ -0,0 +1,146 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type CreateInput struct {
+ // This header specifies the maximum size for the file, up to 1 TiB.
+ ContentLength int64
+
+ // The MIME content type of the file
+ // If not specified, the default type is application/octet-stream.
+ ContentType *string
+
+ // Specifies which content encodings have been applied to the file.
+ // This value is returned to the client when the Get File operation is performed
+ // on the file resource and can be used to decode file content.
+ ContentEncoding *string
+
+ // Specifies the natural languages used by this resource.
+ ContentLanguage *string
+
+ // The File service stores this value but does not use or modify it.
+ CacheControl *string
+
+ // Sets the file's MD5 hash.
+ ContentMD5 *string
+
+ // Sets the file’s Content-Disposition header.
+ ContentDisposition *string
+
+ MetaData map[string]string
+}
+
+// Create creates a new file or replaces a file.
+func (client Client) Create(ctx context.Context, accountName, shareName, path, fileName string, input CreateInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "Create", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "Create", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "Create", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "Create", "`fileName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("files.Client", "Create", "`input.MetaData` cannot be an empty string.")
+ }
+
+ req, err := client.CreatePreparer(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "Create", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "Create", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "Create", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreatePreparer prepares the Create request.
+func (client Client) CreatePreparer(ctx context.Context, accountName, shareName, path, fileName string, input CreateInput) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-content-length": input.ContentLength,
+ "x-ms-type": "file",
+ }
+
+ if input.ContentDisposition != nil {
+ headers["x-ms-content-disposition"] = *input.ContentDisposition
+ }
+
+ if input.ContentEncoding != nil {
+ headers["x-ms-content-encoding"] = *input.ContentEncoding
+ }
+
+ if input.ContentMD5 != nil {
+ headers["x-ms-content-md5"] = *input.ContentMD5
+ }
+
+ if input.ContentType != nil {
+ headers["x-ms-content-type"] = *input.ContentType
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSender sends the Create request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateResponder handles the response to the Create request. The method always
+// closes the http.Response Body.
+func (client Client) CreateResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/files/delete.go b/storage/2017-07-29/file/files/delete.go
new file mode 100644
index 0000000..5debd76
--- /dev/null
+++ b/storage/2017-07-29/file/files/delete.go
@@ -0,0 +1,94 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete immediately deletes the file from the File Share.
+func (client Client) Delete(ctx context.Context, accountName, shareName, path, fileName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "Delete", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "Delete", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "Delete", "`fileName` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, shareName, path, fileName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, shareName, path, fileName string) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/files/lifecycle_test.go b/storage/2017-07-29/file/files/lifecycle_test.go
new file mode 100644
index 0000000..714c802
--- /dev/null
+++ b/storage/2017-07-29/file/files/lifecycle_test.go
@@ -0,0 +1,144 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/file/shares"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestFilesLifeCycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := shares.NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ input := shares.CreateInput{
+ QuotaInGB: 1,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+ defer sharesClient.Delete(ctx, accountName, shareName, false)
+
+ filesClient := NewWithEnvironment(client.Environment)
+ filesClient.Client = client.PrepareWithAuthorizer(filesClient.Client, storageAuth)
+
+ fileName := "bled5.png"
+ contentEncoding := "application/vnd+panda"
+
+ t.Logf("[DEBUG] Creating Top Level File..")
+ createInput := CreateInput{
+ ContentLength: 1024,
+ ContentEncoding: &contentEncoding,
+ }
+ if _, err := filesClient.Create(ctx, accountName, shareName, "", fileName, createInput); err != nil {
+ t.Fatalf("Error creating Top-Level File: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Properties for the Top-Level File..")
+ file, err := filesClient.GetProperties(ctx, accountName, shareName, "", fileName)
+ if err != nil {
+ t.Fatalf("Error retrieving Top-Level File: %s", err)
+ }
+
+ if *file.ContentLength != 1024 {
+ t.Fatalf("Expected the Content-Length to be 1024 but got %d", *file.ContentLength)
+ }
+
+ if file.ContentEncoding != contentEncoding {
+ t.Fatalf("Expected the Content-Encoding to be %q but got %q", contentEncoding, file.ContentEncoding)
+ }
+
+ updatedSize := int64(2048)
+ updatedEncoding := "application/vnd+pandas2"
+ updatedInput := SetPropertiesInput{
+ ContentEncoding: &updatedEncoding,
+ ContentLength: &updatedSize,
+ }
+ if _, err := filesClient.SetProperties(ctx, accountName, shareName, "", fileName, updatedInput); err != nil {
+ t.Fatalf("Error setting properties: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-retrieving Properties for the Top-Level File..")
+ file, err = filesClient.GetProperties(ctx, accountName, shareName, "", fileName)
+ if err != nil {
+ t.Fatalf("Error retrieving Top-Level File: %s", err)
+ }
+
+ if *file.ContentLength != 2048 {
+ t.Fatalf("Expected the Content-Length to be 1024 but got %d", *file.ContentLength)
+ }
+
+ if file.ContentEncoding != updatedEncoding {
+ t.Fatalf("Expected the Content-Encoding to be %q but got %q", updatedEncoding, file.ContentEncoding)
+ }
+
+ t.Logf("[DEBUG] Setting MetaData..")
+ metaData := map[string]string{
+ "hello": "there",
+ }
+ if _, err := filesClient.SetMetaData(ctx, accountName, shareName, "", fileName, metaData); err != nil {
+ t.Fatalf("Error setting MetaData: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving MetaData..")
+ retrievedMetaData, err := filesClient.GetMetaData(ctx, accountName, shareName, "", fileName)
+ if err != nil {
+ t.Fatalf("Error retrieving MetaData: %s", err)
+ }
+ if len(retrievedMetaData.MetaData) != 1 {
+ t.Fatalf("Expected 1 item but got %d", len(retrievedMetaData.MetaData))
+ }
+ if retrievedMetaData.MetaData["hello"] != "there" {
+ t.Fatalf("Expected `hello` to be `there` but got %q", retrievedMetaData.MetaData["hello"])
+ }
+
+ t.Logf("[DEBUG] Re-Setting MetaData..")
+ metaData = map[string]string{
+ "hello": "there",
+ "second": "thing",
+ }
+ if _, err := filesClient.SetMetaData(ctx, accountName, shareName, "", fileName, metaData); err != nil {
+ t.Fatalf("Error setting MetaData: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-Retrieving MetaData..")
+ retrievedMetaData, err = filesClient.GetMetaData(ctx, accountName, shareName, "", fileName)
+ if err != nil {
+ t.Fatalf("Error retrieving MetaData: %s", err)
+ }
+ if len(retrievedMetaData.MetaData) != 2 {
+ t.Fatalf("Expected 2 items but got %d", len(retrievedMetaData.MetaData))
+ }
+ if retrievedMetaData.MetaData["hello"] != "there" {
+ t.Fatalf("Expected `hello` to be `there` but got %q", retrievedMetaData.MetaData["hello"])
+ }
+ if retrievedMetaData.MetaData["second"] != "thing" {
+ t.Fatalf("Expected `second` to be `thing` but got %q", retrievedMetaData.MetaData["second"])
+ }
+
+ t.Logf("[DEBUG] Deleting Top Level File..")
+ if _, err := filesClient.Delete(ctx, accountName, shareName, "", fileName); err != nil {
+ t.Fatalf("Error deleting Top-Level File: %s", err)
+ }
+}
diff --git a/storage/2017-07-29/file/files/metadata_get.go b/storage/2017-07-29/file/files/metadata_get.go
new file mode 100644
index 0000000..fd62f90
--- /dev/null
+++ b/storage/2017-07-29/file/files/metadata_get.go
@@ -0,0 +1,111 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetMetaDataResult struct {
+ autorest.Response
+
+ MetaData map[string]string
+}
+
+// GetMetaData returns the MetaData for the specified File.
+func (client Client) GetMetaData(ctx context.Context, accountName, shareName, path, fileName string) (result GetMetaDataResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "GetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "GetMetaData", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "GetMetaData", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "GetMetaData", "`fileName` cannot be an empty string.")
+ }
+
+ req, err := client.GetMetaDataPreparer(ctx, accountName, shareName, path, fileName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "GetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetMetaDataSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "GetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "GetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetMetaDataPreparer prepares the GetMetaData request.
+func (client Client) GetMetaDataPreparer(ctx context.Context, accountName, shareName, path, fileName string) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetMetaDataSender sends the GetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetMetaDataResponder handles the response to the GetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) GetMetaDataResponder(resp *http.Response) (result GetMetaDataResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ //metadata.ByParsingFromHeaders(&result.MetaData),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/files/metadata_set.go b/storage/2017-07-29/file/files/metadata_set.go
new file mode 100644
index 0000000..41e3ffc
--- /dev/null
+++ b/storage/2017-07-29/file/files/metadata_set.go
@@ -0,0 +1,105 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// SetMetaData updates the specified File to have the specified MetaData.
+func (client Client) SetMetaData(ctx context.Context, accountName, shareName, path, fileName string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "SetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "SetMetaData", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "SetMetaData", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "SetMetaData", "`fileName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("files.Client", "SetMetaData", fmt.Sprintf("`metaData` is not valid: %s.", err))
+ }
+
+ req, err := client.SetMetaDataPreparer(ctx, accountName, shareName, path, fileName, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "SetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetMetaDataSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "SetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "SetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetMetaDataPreparer prepares the SetMetaData request.
+func (client Client) SetMetaDataPreparer(ctx context.Context, accountName, shareName, path, fileName string, metaData map[string]string) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetMetaDataSender sends the SetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetMetaDataResponder handles the response to the SetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) SetMetaDataResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/files/properties_get.go b/storage/2017-07-29/file/files/properties_get.go
new file mode 100644
index 0000000..c6a0c39
--- /dev/null
+++ b/storage/2017-07-29/file/files/properties_get.go
@@ -0,0 +1,144 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetResult struct {
+ autorest.Response
+
+ CacheControl string
+ ContentDisposition string
+ ContentEncoding string
+ ContentLanguage string
+ ContentLength *int64
+ ContentMD5 string
+ ContentType string
+ CopyID string
+ CopyStatus string
+ CopySource string
+ CopyProgress string
+ CopyStatusDescription string
+ CopyCompletionTime string
+ Encrypted bool
+
+ MetaData map[string]string
+}
+
+// GetProperties returns the Properties for the specified file
+func (client Client) GetProperties(ctx context.Context, accountName, shareName, path, fileName string) (result GetResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "GetProperties", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "GetProperties", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "GetProperties", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "GetProperties", "`fileName` cannot be an empty string.")
+ }
+
+ req, err := client.GetPropertiesPreparer(ctx, accountName, shareName, path, fileName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "GetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetPropertiesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "GetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "GetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPropertiesPreparer prepares the GetProperties request.
+func (client Client) GetPropertiesPreparer(ctx context.Context, accountName, shareName, path, fileName string) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsHead(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetPropertiesSender sends the GetProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetPropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetPropertiesResponder handles the response to the GetProperties request. The method always
+// closes the http.Response Body.
+func (client Client) GetPropertiesResponder(resp *http.Response) (result GetResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.CacheControl = resp.Header.Get("Cache-Control")
+ result.ContentDisposition = resp.Header.Get("Content-Disposition")
+ result.ContentEncoding = resp.Header.Get("Content-Encoding")
+ result.ContentLanguage = resp.Header.Get("Content-Language")
+ result.ContentMD5 = resp.Header.Get("x-ms-content-md5")
+ result.ContentType = resp.Header.Get("Content-Type")
+ result.CopyID = resp.Header.Get("x-ms-copy-id")
+ result.CopyProgress = resp.Header.Get("x-ms-copy-progress")
+ result.CopySource = resp.Header.Get("x-ms-copy-source")
+ result.CopyStatus = resp.Header.Get("x-ms-copy-status")
+ result.CopyStatusDescription = resp.Header.Get("x-ms-copy-status-description")
+ result.CopyCompletionTime = resp.Header.Get("x-ms-copy-completion-time")
+ result.Encrypted = strings.EqualFold(resp.Header.Get("x-ms-server-encrypted"), "true")
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+
+ contentLengthRaw := resp.Header.Get("Content-Length")
+ if contentLengthRaw != "" {
+ contentLength, err := strconv.Atoi(contentLengthRaw)
+ if err != nil {
+ return result, fmt.Errorf("Error parsing %q for Content-Length as an integer: %s", contentLengthRaw, err)
+ }
+ contentLengthI64 := int64(contentLength)
+ result.ContentLength = &contentLengthI64
+ }
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/files/properties_set.go b/storage/2017-07-29/file/files/properties_set.go
new file mode 100644
index 0000000..79fffc2
--- /dev/null
+++ b/storage/2017-07-29/file/files/properties_set.go
@@ -0,0 +1,160 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type SetPropertiesInput struct {
+ // Resizes a file to the specified size.
+ // If the specified byte value is less than the current size of the file,
+ // then all ranges above the specified byte value are cleared.
+ ContentLength *int64
+
+ // Modifies the cache control string for the file.
+ // If this property is not specified on the request, then the property will be cleared for the file.
+ // Subsequent calls to Get File Properties will not return this property,
+ // unless it is explicitly set on the file again.
+ ContentControl *string
+
+ // Sets the file’s Content-Disposition header.
+ // If this property is not specified on the request, then the property will be cleared for the file.
+ // Subsequent calls to Get File Properties will not return this property,
+ // unless it is explicitly set on the file again.
+ ContentDisposition *string
+
+ // Sets the file's content encoding.
+ // If this property is not specified on the request, then the property will be cleared for the file.
+ // Subsequent calls to Get File Properties will not return this property,
+ // unless it is explicitly set on the file again.
+ ContentEncoding *string
+
+ // Sets the file's content language.
+ // If this property is not specified on the request, then the property will be cleared for the file.
+ // Subsequent calls to Get File Properties will not return this property,
+ // unless it is explicitly set on the file again.
+ ContentLanguage *string
+
+ // Sets the file's MD5 hash.
+ // If this property is not specified on the request, then the property will be cleared for the file.
+ // Subsequent calls to Get File Properties will not return this property,
+ // unless it is explicitly set on the file again.
+ ContentMD5 *string
+
+ // Sets the file's content type.
+ // If this property is not specified on the request, then the property will be cleared for the file.
+ // Subsequent calls to Get File Properties will not return this property,
+ // unless it is explicitly set on the file again.
+ ContentType *string
+}
+
+// SetProperties sets the specified properties on the specified File
+func (client Client) SetProperties(ctx context.Context, accountName, shareName, path, fileName string, input SetPropertiesInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "SetProperties", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "SetProperties", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "SetProperties", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "SetProperties", "`fileName` cannot be an empty string.")
+ }
+
+ req, err := client.SetPropertiesPreparer(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "SetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetPropertiesSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "SetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "SetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetPropertiesPreparer prepares the SetProperties request.
+func (client Client) SetPropertiesPreparer(ctx context.Context, accountName, shareName, path, fileName string, input SetPropertiesInput) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-type": "file",
+ }
+
+ if input.ContentControl != nil {
+ headers["x-ms-cache-control"] = *input.ContentControl
+ }
+ if input.ContentDisposition != nil {
+ headers["x-ms-content-disposition"] = *input.ContentDisposition
+ }
+ if input.ContentEncoding != nil {
+ headers["x-ms-content-encoding"] = *input.ContentEncoding
+ }
+ if input.ContentLanguage != nil {
+ headers["x-ms-content-language"] = *input.ContentLanguage
+ }
+ if input.ContentLength != nil {
+ headers["x-ms-content-length"] = *input.ContentLength
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-content-md5"] = *input.ContentMD5
+ }
+ if input.ContentType != nil {
+ headers["x-ms-content-type"] = *input.ContentType
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetPropertiesSender sends the SetProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetPropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetPropertiesResponder handles the response to the SetProperties request. The method always
+// closes the http.Response Body.
+func (client Client) SetPropertiesResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/files/range_clear.go b/storage/2017-07-29/file/files/range_clear.go
new file mode 100644
index 0000000..5d8145f
--- /dev/null
+++ b/storage/2017-07-29/file/files/range_clear.go
@@ -0,0 +1,112 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type ClearByteRangeInput struct {
+ StartBytes int64
+ EndBytes int64
+}
+
+// ClearByteRange clears the specified Byte Range from within the specified File
+func (client Client) ClearByteRange(ctx context.Context, accountName, shareName, path, fileName string, input ClearByteRangeInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "ClearByteRange", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "ClearByteRange", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "ClearByteRange", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "ClearByteRange", "`fileName` cannot be an empty string.")
+ }
+ if input.StartBytes < 0 {
+ return result, validation.NewError("files.Client", "ClearByteRange", "`input.StartBytes` must be greater or equal to 0.")
+ }
+ if input.EndBytes <= 0 {
+ return result, validation.NewError("files.Client", "ClearByteRange", "`input.EndBytes` must be greater than 0.")
+ }
+
+ req, err := client.ClearByteRangePreparer(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "ClearByteRange", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ClearByteRangeSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "ClearByteRange", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ClearByteRangeResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "ClearByteRange", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ClearByteRangePreparer prepares the ClearByteRange request.
+func (client Client) ClearByteRangePreparer(ctx context.Context, accountName, shareName, path, fileName string, input ClearByteRangeInput) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "range"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-write": "clear",
+ "x-ms-range": fmt.Sprintf("bytes=%d-%d", input.StartBytes, input.EndBytes),
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ClearByteRangeSender sends the ClearByteRange request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ClearByteRangeSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ClearByteRangeResponder handles the response to the ClearByteRange request. The method always
+// closes the http.Response Body.
+func (client Client) ClearByteRangeResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/files/range_get.go b/storage/2017-07-29/file/files/range_get.go
new file mode 100644
index 0000000..733d3f5
--- /dev/null
+++ b/storage/2017-07-29/file/files/range_get.go
@@ -0,0 +1,121 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetByteRangeInput struct {
+ StartBytes int64
+ EndBytes int64
+}
+
+type GetByteRangeResult struct {
+ autorest.Response
+
+ Contents []byte
+}
+
+// GetByteRange returns the specified Byte Range from the specified File.
+func (client Client) GetByteRange(ctx context.Context, accountName, shareName, path, fileName string, input GetByteRangeInput) (result GetByteRangeResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "GetByteRange", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "GetByteRange", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "GetByteRange", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "GetByteRange", "`fileName` cannot be an empty string.")
+ }
+ if input.StartBytes < 0 {
+ return result, validation.NewError("files.Client", "GetByteRange", "`input.StartBytes` must be greater or equal to 0.")
+ }
+ if input.EndBytes <= 0 {
+ return result, validation.NewError("files.Client", "GetByteRange", "`input.EndBytes` must be greater than 0.")
+ }
+ expectedBytes := input.EndBytes - input.StartBytes
+ if expectedBytes < (4 * 1024) {
+ return result, validation.NewError("files.Client", "GetByteRange", "Requested Byte Range must be at least 4KB.")
+ }
+ if expectedBytes > (4 * 1024 * 1024) {
+ return result, validation.NewError("files.Client", "GetByteRange", "Requested Byte Range must be at most 4MB.")
+ }
+
+ req, err := client.GetByteRangePreparer(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "GetByteRange", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetByteRangeSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "GetByteRange", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetByteRangeResponder(resp, expectedBytes)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "GetByteRange", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetByteRangePreparer prepares the GetByteRange request.
+func (client Client) GetByteRangePreparer(ctx context.Context, accountName, shareName, path, fileName string, input GetByteRangeInput) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-range": fmt.Sprintf("bytes=%d-%d", input.StartBytes, input.EndBytes-1),
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetByteRangeSender sends the GetByteRange request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetByteRangeSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetByteRangeResponder handles the response to the GetByteRange request. The method always
+// closes the http.Response Body.
+func (client Client) GetByteRangeResponder(resp *http.Response, length int64) (result GetByteRangeResult, err error) {
+ result.Contents = make([]byte, length)
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusPartialContent),
+ autorest.ByUnmarshallingBytes(&result.Contents),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/files/range_get_file.go b/storage/2017-07-29/file/files/range_get_file.go
new file mode 100644
index 0000000..9e5be17
--- /dev/null
+++ b/storage/2017-07-29/file/files/range_get_file.go
@@ -0,0 +1,128 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "math"
+ "runtime"
+ "sync"
+
+ "github.com/Azure/go-autorest/autorest"
+)
+
+// GetFile is a helper method to download a file by chunking it automatically
+func (client Client) GetFile(ctx context.Context, accountName, shareName, path, fileName string, parallelism int) (result autorest.Response, outputBytes []byte, err error) {
+
+ // first look up the file and check out how many bytes it is
+ file, e := client.GetProperties(ctx, accountName, shareName, path, fileName)
+ if err != nil {
+ result = file.Response
+ err = e
+ return
+ }
+
+ if file.ContentLength == nil {
+ err = fmt.Errorf("Content-Length was nil!")
+ return
+ }
+
+ length := int64(*file.ContentLength)
+ chunkSize := int64(4 * 1024 * 1024) // 4MB
+
+ if chunkSize > length {
+ chunkSize = length
+ }
+
+ // then split that up into chunks and retrieve it retrieve it into the 'results' set
+ chunks := int(math.Ceil(float64(length) / float64(chunkSize)))
+ workerCount := parallelism * runtime.NumCPU()
+ if workerCount > chunks {
+ workerCount = chunks
+ }
+
+ var waitGroup sync.WaitGroup
+ waitGroup.Add(workerCount)
+
+ results := make([]*downloadFileChunkResult, chunks)
+ errors := make(chan error, chunkSize)
+
+ for i := 0; i < chunks; i++ {
+ go func(i int) {
+ log.Printf("[DEBUG] Downloading Chunk %d of %d", i+1, chunks)
+
+ dfci := downloadFileChunkInput{
+ thisChunk: i,
+ chunkSize: chunkSize,
+ fileSize: length,
+ }
+
+ result, err := client.downloadFileChunk(ctx, accountName, shareName, path, fileName, dfci)
+ if err != nil {
+ errors <- err
+ waitGroup.Done()
+ return
+ }
+
+ // if there's no error, we should have bytes, so this is safe
+ results[i] = result
+
+ waitGroup.Done()
+ }(i)
+ }
+ waitGroup.Wait()
+
+ // TODO: we should switch to hashicorp/multi-error here
+ if len(errors) > 0 {
+ err = fmt.Errorf("Error downloading file: %s", <-errors)
+ return
+ }
+
+ // then finally put it all together, in order and return it
+ output := make([]byte, length)
+ for _, v := range results {
+ copy(output[v.startBytes:v.endBytes], v.bytes)
+ }
+
+ outputBytes = output
+ return
+}
+
+type downloadFileChunkInput struct {
+ thisChunk int
+ chunkSize int64
+ fileSize int64
+}
+
+type downloadFileChunkResult struct {
+ startBytes int64
+ endBytes int64
+ bytes []byte
+}
+
+func (client Client) downloadFileChunk(ctx context.Context, accountName, shareName, path, fileName string, input downloadFileChunkInput) (*downloadFileChunkResult, error) {
+ startBytes := input.chunkSize * int64(input.thisChunk)
+ endBytes := startBytes + input.chunkSize
+
+ // the last chunk may exceed the size of the file
+ remaining := input.fileSize - startBytes
+ if input.chunkSize > remaining {
+ endBytes = startBytes + remaining
+ }
+
+ getInput := GetByteRangeInput{
+ StartBytes: startBytes,
+ EndBytes: endBytes,
+ }
+ result, err := client.GetByteRange(ctx, accountName, shareName, path, fileName, getInput)
+ if err != nil {
+ return nil, fmt.Errorf("Error putting bytes: %s", err)
+ }
+
+ output := downloadFileChunkResult{
+ startBytes: startBytes,
+ endBytes: endBytes,
+ bytes: result.Contents,
+ }
+ return &output, nil
+}
diff --git a/storage/2017-07-29/file/files/range_get_file_test.go b/storage/2017-07-29/file/files/range_get_file_test.go
new file mode 100644
index 0000000..3c0162a
--- /dev/null
+++ b/storage/2017-07-29/file/files/range_get_file_test.go
@@ -0,0 +1,108 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/file/shares"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestGetSmallFile(t *testing.T) {
+ // the purpose of this test is to verify that the small, single-chunked file gets downloaded correctly
+ testGetFile(t, "small-file.png", "image/png")
+}
+
+func TestGetLargeFile(t *testing.T) {
+ // the purpose of this test is to verify that the large, multi-chunked file gets downloaded correctly
+ testGetFile(t, "blank-large-file.dmg", "application/x-apple-diskimage")
+}
+
+func testGetFile(t *testing.T, fileName string, contentType string) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := shares.NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ input := shares.CreateInput{
+ QuotaInGB: 10,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+ defer sharesClient.Delete(ctx, accountName, shareName, false)
+
+ filesClient := NewWithEnvironment(client.Environment)
+ filesClient.Client = client.PrepareWithAuthorizer(filesClient.Client, storageAuth)
+
+ // store files outside of this directory, since they're reused
+ file, err := os.Open("../../../testdata/" + fileName)
+ if err != nil {
+ t.Fatalf("Error opening: %s", err)
+ }
+
+ info, err := file.Stat()
+ if err != nil {
+ t.Fatalf("Error 'stat'-ing: %s", err)
+ }
+
+ t.Logf("[DEBUG] Creating Top Level File..")
+ createFileInput := CreateInput{
+ ContentLength: info.Size(),
+ ContentType: &contentType,
+ }
+ if _, err := filesClient.Create(ctx, accountName, shareName, "", fileName, createFileInput); err != nil {
+ t.Fatalf("Error creating Top-Level File: %s", err)
+ }
+
+ t.Logf("[DEBUG] Uploading File..")
+ if err := filesClient.PutFile(ctx, accountName, shareName, "", fileName, file, 4); err != nil {
+ t.Fatalf("Error uploading File: %s", err)
+ }
+
+ t.Logf("[DEBUG] Downloading file..")
+ _, downloadedBytes, err := filesClient.GetFile(ctx, accountName, shareName, "", fileName, 4)
+ if err != nil {
+ t.Fatalf("Error downloading file: %s", err)
+ }
+
+ t.Logf("[DEBUG] Asserting the files are the same size..")
+ expectedBytes := make([]byte, info.Size())
+ file.Read(expectedBytes)
+ if len(expectedBytes) != len(downloadedBytes) {
+ t.Fatalf("Expected %d bytes but got %d", len(expectedBytes), len(downloadedBytes))
+ }
+
+ t.Logf("[DEBUG] Asserting the files are the same content-wise..")
+ // overkill, but it's this or shasum-ing
+ for i := int64(0); i < info.Size(); i++ {
+ if expectedBytes[i] != downloadedBytes[i] {
+ t.Fatalf("Expected byte %d to be %q but got %q", i, expectedBytes[i], downloadedBytes[i])
+ }
+ }
+
+ t.Logf("[DEBUG] Deleting Top Level File..")
+ if _, err := filesClient.Delete(ctx, accountName, shareName, "", fileName); err != nil {
+ t.Fatalf("Error deleting Top-Level File: %s", err)
+ }
+
+}
diff --git a/storage/2017-07-29/file/files/range_put.go b/storage/2017-07-29/file/files/range_put.go
new file mode 100644
index 0000000..77fe101
--- /dev/null
+++ b/storage/2017-07-29/file/files/range_put.go
@@ -0,0 +1,129 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type PutByteRangeInput struct {
+ StartBytes int64
+ EndBytes int64
+
+ // Content is the File Contents for the specified range
+ // which can be at most 4MB
+ Content []byte
+}
+
+// PutByteRange puts the specified Byte Range in the specified File.
+func (client Client) PutByteRange(ctx context.Context, accountName, shareName, path, fileName string, input PutByteRangeInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "PutByteRange", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "PutByteRange", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "PutByteRange", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "PutByteRange", "`fileName` cannot be an empty string.")
+ }
+ if input.StartBytes < 0 {
+ return result, validation.NewError("files.Client", "PutByteRange", "`input.StartBytes` must be greater or equal to 0.")
+ }
+ if input.EndBytes <= 0 {
+ return result, validation.NewError("files.Client", "PutByteRange", "`input.EndBytes` must be greater than 0.")
+ }
+
+ expectedBytes := input.EndBytes - input.StartBytes
+ actualBytes := len(input.Content)
+ if expectedBytes != int64(actualBytes) {
+ return result, validation.NewError("files.Client", "PutByteRange", fmt.Sprintf("The specified byte-range (%d) didn't match the content size (%d).", expectedBytes, actualBytes))
+ }
+ if expectedBytes < (4 * 1024) {
+ return result, validation.NewError("files.Client", "PutByteRange", "Specified Byte Range must be at least 4KB.")
+ }
+
+ if expectedBytes > (4 * 1024 * 1024) {
+ return result, validation.NewError("files.Client", "PutByteRange", "Specified Byte Range must be at most 4MB.")
+ }
+
+ req, err := client.PutByteRangePreparer(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "PutByteRange", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutByteRangeSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "PutByteRange", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutByteRangeResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "PutByteRange", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutByteRangePreparer prepares the PutByteRange request.
+func (client Client) PutByteRangePreparer(ctx context.Context, accountName, shareName, path, fileName string, input PutByteRangeInput) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "range"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-write": "update",
+ "x-ms-range": fmt.Sprintf("bytes=%d-%d", input.StartBytes, input.EndBytes-1),
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithBytes(&input.Content))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutByteRangeSender sends the PutByteRange request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutByteRangeSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutByteRangeResponder handles the response to the PutByteRange request. The method always
+// closes the http.Response Body.
+func (client Client) PutByteRangeResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/files/range_put_file.go b/storage/2017-07-29/file/files/range_put_file.go
new file mode 100644
index 0000000..a39cd37
--- /dev/null
+++ b/storage/2017-07-29/file/files/range_put_file.go
@@ -0,0 +1,107 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "log"
+ "math"
+ "os"
+ "runtime"
+ "sync"
+
+ "github.com/Azure/go-autorest/autorest"
+)
+
+// PutFile is a helper method which takes a file, and automatically chunks it up, rather than having to do this yourself
+func (client Client) PutFile(ctx context.Context, accountName, shareName, path, fileName string, file *os.File, parallelism int) error {
+ fileInfo, err := file.Stat()
+ if err != nil {
+ return fmt.Errorf("Error loading file info: %s", err)
+ }
+
+ fileSize := fileInfo.Size()
+ chunkSize := 4 * 1024 * 1024 // 4MB
+ if chunkSize > int(fileSize) {
+ chunkSize = int(fileSize)
+ }
+ chunks := int(math.Ceil(float64(fileSize) / float64(chunkSize*1.0)))
+
+ workerCount := parallelism * runtime.NumCPU()
+ if workerCount > chunks {
+ workerCount = chunks
+ }
+
+ var waitGroup sync.WaitGroup
+ waitGroup.Add(workerCount)
+ errors := make(chan error, chunkSize)
+
+ for i := 0; i < chunks; i++ {
+ go func(i int) {
+ log.Printf("[DEBUG] Chunk %d of %d", i+1, chunks)
+
+ uci := uploadChunkInput{
+ thisChunk: i,
+ chunkSize: chunkSize,
+ fileSize: fileSize,
+ }
+
+ _, err := client.uploadChunk(ctx, accountName, shareName, path, fileName, uci, file)
+ if err != nil {
+ errors <- err
+ waitGroup.Done()
+ return
+ }
+
+ waitGroup.Done()
+ return
+ }(i)
+ }
+ waitGroup.Wait()
+
+ // TODO: we should switch to hashicorp/multi-error here
+ if len(errors) > 0 {
+ return fmt.Errorf("Error uploading file: %s", <-errors)
+ }
+
+ return nil
+}
+
+type uploadChunkInput struct {
+ thisChunk int
+ chunkSize int
+ fileSize int64
+}
+
+func (client Client) uploadChunk(ctx context.Context, accountName, shareName, path, fileName string, input uploadChunkInput, file *os.File) (result autorest.Response, err error) {
+ startBytes := int64(input.chunkSize * input.thisChunk)
+ endBytes := startBytes + int64(input.chunkSize)
+
+ // the last size may exceed the size of the file
+ remaining := input.fileSize - startBytes
+ if int64(input.chunkSize) > remaining {
+ endBytes = startBytes + remaining
+ }
+
+ bytesToRead := int(endBytes) - int(startBytes)
+ bytes := make([]byte, bytesToRead)
+
+ _, err = file.ReadAt(bytes, startBytes)
+ if err != nil {
+ if err != io.EOF {
+ return result, fmt.Errorf("Error reading bytes: %s", err)
+ }
+ }
+
+ putBytesInput := PutByteRangeInput{
+ StartBytes: startBytes,
+ EndBytes: endBytes,
+ Content: bytes,
+ }
+ result, err = client.PutByteRange(ctx, accountName, shareName, path, fileName, putBytesInput)
+ if err != nil {
+ return result, fmt.Errorf("Error putting bytes: %s", err)
+ }
+
+ return
+}
diff --git a/storage/2017-07-29/file/files/range_put_file_test.go b/storage/2017-07-29/file/files/range_put_file_test.go
new file mode 100644
index 0000000..6708614
--- /dev/null
+++ b/storage/2017-07-29/file/files/range_put_file_test.go
@@ -0,0 +1,86 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/file/shares"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestPutSmallFile(t *testing.T) {
+ // the purpose of this test is to ensure that a small file (< 4MB) is a single chunk
+ testPutFile(t, "small-file.png", "image/png")
+}
+
+func TestPutLargeFile(t *testing.T) {
+ // the purpose of this test is to ensure that large files (> 4MB) are chunked
+ testPutFile(t, "blank-large-file.dmg", "application/x-apple-diskimage")
+}
+
+func testPutFile(t *testing.T, fileName string, contentType string) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := shares.NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ input := shares.CreateInput{
+ QuotaInGB: 10,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+ defer sharesClient.Delete(ctx, accountName, shareName, false)
+
+ filesClient := NewWithEnvironment(client.Environment)
+ filesClient.Client = client.PrepareWithAuthorizer(filesClient.Client, storageAuth)
+
+ // store files outside of this directory, since they're reused
+ file, err := os.Open("../../../testdata/" + fileName)
+ if err != nil {
+ t.Fatalf("Error opening: %s", err)
+ }
+
+ info, err := file.Stat()
+ if err != nil {
+ t.Fatalf("Error 'stat'-ing: %s", err)
+ }
+
+ t.Logf("[DEBUG] Creating Top Level File..")
+ createFileInput := CreateInput{
+ ContentLength: info.Size(),
+ ContentType: &contentType,
+ }
+ if _, err := filesClient.Create(ctx, accountName, shareName, "", fileName, createFileInput); err != nil {
+ t.Fatalf("Error creating Top-Level File: %s", err)
+ }
+
+ t.Logf("[DEBUG] Uploading File..")
+ if err := filesClient.PutFile(ctx, accountName, shareName, "", fileName, file, 4); err != nil {
+ t.Fatalf("Error uploading File: %s", err)
+ }
+
+ t.Logf("[DEBUG] Deleting Top Level File..")
+ if _, err := filesClient.Delete(ctx, accountName, shareName, "", fileName); err != nil {
+ t.Fatalf("Error deleting Top-Level File: %s", err)
+ }
+}
diff --git a/storage/2017-07-29/file/files/ranges_list.go b/storage/2017-07-29/file/files/ranges_list.go
new file mode 100644
index 0000000..ea309f9
--- /dev/null
+++ b/storage/2017-07-29/file/files/ranges_list.go
@@ -0,0 +1,114 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type ListRangesResult struct {
+ autorest.Response
+
+ Ranges []Range `xml:"Range"`
+}
+
+type Range struct {
+ Start string `xml:"Start"`
+ End string `xml:"End"`
+}
+
+// ListRanges returns the list of valid ranges for the specified File.
+func (client Client) ListRanges(ctx context.Context, accountName, shareName, path, fileName string) (result ListRangesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "ListRanges", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "ListRanges", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "ListRanges", "`shareName` must be a lower-cased string.")
+ }
+ if path == "" {
+ return result, validation.NewError("files.Client", "ListRanges", "`path` cannot be an empty string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "ListRanges", "`fileName` cannot be an empty string.")
+ }
+
+ req, err := client.ListRangesPreparer(ctx, accountName, shareName, path, fileName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "ListRanges", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ListRangesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "ListRanges", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ListRangesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "ListRanges", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ListRangesPreparer prepares the ListRanges request.
+func (client Client) ListRangesPreparer(ctx context.Context, accountName, shareName, path, fileName string) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "rangelist"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ListRangesSender sends the ListRanges request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ListRangesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ListRangesResponder handles the response to the ListRanges request. The method always
+// closes the http.Response Body.
+func (client Client) ListRangesResponder(resp *http.Response) (result ListRangesResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/files/resource_id.go b/storage/2017-07-29/file/files/resource_id.go
new file mode 100644
index 0000000..ed1208d
--- /dev/null
+++ b/storage/2017-07-29/file/files/resource_id.go
@@ -0,0 +1,64 @@
+package files
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given File
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, shareName, directoryName, filePath string) string {
+ domain := endpoints.GetFileEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s/%s/%s", domain, shareName, directoryName, filePath)
+}
+
+type ResourceID struct {
+ AccountName string
+ DirectoryName string
+ FileName string
+ ShareName string
+}
+
+// ParseResourceID parses the specified Resource ID and returns an object
+// which can be used to interact with Files within a Storage Share.
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://account1.file.core.chinacloudapi.cn/share1/directory1/file1.txt
+ // example: https://account1.file.core.chinacloudapi.cn/share1/directory1/directory2/file1.txt
+
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ path := strings.TrimPrefix(uri.Path, "/")
+ segments := strings.Split(path, "/")
+ if len(segments) == 0 {
+ return nil, fmt.Errorf("Expected the path to contain segments but got none")
+ }
+
+ shareName := segments[0]
+ fileName := segments[len(segments)-1]
+
+ directoryName := strings.TrimPrefix(path, shareName)
+ directoryName = strings.TrimPrefix(directoryName, "/")
+ directoryName = strings.TrimSuffix(directoryName, fileName)
+ directoryName = strings.TrimSuffix(directoryName, "/")
+ return &ResourceID{
+ AccountName: *accountName,
+ ShareName: shareName,
+ DirectoryName: directoryName,
+ FileName: fileName,
+ }, nil
+}
diff --git a/storage/2017-07-29/file/files/resource_id_test.go b/storage/2017-07-29/file/files/resource_id_test.go
new file mode 100644
index 0000000..1b521ac
--- /dev/null
+++ b/storage/2017-07-29/file/files/resource_id_test.go
@@ -0,0 +1,131 @@
+package files
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.file.core.chinacloudapi.cn/share1/directory1/file1.txt",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.file.core.cloudapi.de/share1/directory1/file1.txt",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.file.core.windows.net/share1/directory1/file1.txt",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.file.core.usgovcloudapi.net/share1/directory1/file1.txt",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "share1", "directory1", "file1.txt")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.file.core.chinacloudapi.cn/share1/directory1/file1.txt",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.file.core.cloudapi.de/share1/directory1/file1.txt",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.file.core.windows.net/share1/directory1/file1.txt",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.file.core.usgovcloudapi.net/share1/directory1/file1.txt",
+ },
+ }
+
+ t.Logf("[DEBUG] Top Level Files")
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.ShareName != "share1" {
+ t.Fatalf("Expected Share Name to be `share1` but got %q", actual.ShareName)
+ }
+ if actual.DirectoryName != "directory1" {
+ t.Fatalf("Expected Directory Name to be `directory1` but got %q", actual.DirectoryName)
+ }
+ if actual.FileName != "file1.txt" {
+ t.Fatalf("Expected File Name to be `file1.txt` but got %q", actual.FileName)
+ }
+ }
+
+ testData = []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.file.core.chinacloudapi.cn/share1/directory1/directory2/file1.txt",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.file.core.cloudapi.de/share1/directory1/directory2/file1.txt",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.file.core.windows.net/share1/directory1/directory2/file1.txt",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.file.core.usgovcloudapi.net/share1/directory1/directory2/file1.txt",
+ },
+ }
+
+ t.Logf("[DEBUG] Nested Files")
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.ShareName != "share1" {
+ t.Fatalf("Expected Share Name to be `share1` but got %q", actual.ShareName)
+ }
+ if actual.DirectoryName != "directory1/directory2" {
+ t.Fatalf("Expected Directory Name to be `directory1/directory2` but got %q", actual.DirectoryName)
+ }
+ if actual.FileName != "file1.txt" {
+ t.Fatalf("Expected File Name to be `file1.txt` but got %q", actual.FileName)
+ }
+ }
+}
diff --git a/storage/2017-07-29/file/files/version.go b/storage/2017-07-29/file/files/version.go
new file mode 100644
index 0000000..c3604d3
--- /dev/null
+++ b/storage/2017-07-29/file/files/version.go
@@ -0,0 +1,14 @@
+package files
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2017-07-29"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2017-07-29/file/shares/README.md b/storage/2017-07-29/file/shares/README.md
new file mode 100644
index 0000000..53c72a0
--- /dev/null
+++ b/storage/2017-07-29/file/shares/README.md
@@ -0,0 +1,42 @@
+## File Storage Shares SDK for API version 2017-07-29
+
+This package allows you to interact with the Shares File Storage API
+
+### Supported Authorizers
+
+* SharedKeyLite (Blob, File & Queue)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/file/shares"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ shareName := "myshare"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ sharesClient := shares.New()
+ sharesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ input := shares.CreateInput{
+ QuotaInGB: 2,
+ }
+ if _, err := sharesClient.Create(ctx, accountName, shareName, input); err != nil {
+ return fmt.Errorf("Error creating Share: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2017-07-29/file/shares/acl_get.go b/storage/2017-07-29/file/shares/acl_get.go
new file mode 100644
index 0000000..ea6ff4c
--- /dev/null
+++ b/storage/2017-07-29/file/shares/acl_get.go
@@ -0,0 +1,98 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetACLResult struct {
+ autorest.Response
+
+ SignedIdentifiers []SignedIdentifier `xml:"SignedIdentifier"`
+}
+
+// GetACL get the Access Control List for the specified Storage Share
+func (client Client) GetACL(ctx context.Context, accountName, shareName string) (result GetACLResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "GetACL", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "GetACL", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "GetACL", "`shareName` must be a lower-cased string.")
+ }
+
+ req, err := client.GetACLPreparer(ctx, accountName, shareName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetACL", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetACLSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetACL", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetACLResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetACL", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetACLPreparer prepares the GetACL request.
+func (client Client) GetACLPreparer(ctx context.Context, accountName, shareName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "comp": autorest.Encode("query", "acl"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetACLSender sends the GetACL request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetACLSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetACLResponder handles the response to the GetACL request. The method always
+// closes the http.Response Body.
+func (client Client) GetACLResponder(resp *http.Response) (result GetACLResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/shares/acl_set.go b/storage/2017-07-29/file/shares/acl_set.go
new file mode 100644
index 0000000..18d1788
--- /dev/null
+++ b/storage/2017-07-29/file/shares/acl_set.go
@@ -0,0 +1,103 @@
+package shares
+
+import (
+ "context"
+ "encoding/xml"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type setAcl struct {
+ SignedIdentifiers []SignedIdentifier `xml:"SignedIdentifier"`
+
+ XMLName xml.Name `xml:"SignedIdentifiers"`
+}
+
+// SetACL sets the specified Access Control List on the specified Storage Share
+func (client Client) SetACL(ctx context.Context, accountName, shareName string, acls []SignedIdentifier) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "SetACL", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "SetACL", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "SetACL", "`shareName` must be a lower-cased string.")
+ }
+
+ req, err := client.SetACLPreparer(ctx, accountName, shareName, acls)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetACL", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetACLSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetACL", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetACLResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetACL", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetACLPreparer prepares the SetACL request.
+func (client Client) SetACLPreparer(ctx context.Context, accountName, shareName string, acls []SignedIdentifier) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "comp": autorest.Encode("query", "acl"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ input := setAcl{
+ SignedIdentifiers: acls,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithXML(&input))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetACLSender sends the SetACL request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetACLSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetACLResponder handles the response to the SetACL request. The method always
+// closes the http.Response Body.
+func (client Client) SetACLResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/shares/client.go b/storage/2017-07-29/file/shares/client.go
new file mode 100644
index 0000000..4f3a6f9
--- /dev/null
+++ b/storage/2017-07-29/file/shares/client.go
@@ -0,0 +1,25 @@
+package shares
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for File Storage Shares.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2017-07-29/file/shares/create.go b/storage/2017-07-29/file/shares/create.go
new file mode 100644
index 0000000..84fd40d
--- /dev/null
+++ b/storage/2017-07-29/file/shares/create.go
@@ -0,0 +1,109 @@
+package shares
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type CreateInput struct {
+ // Specifies the maximum size of the share, in gigabytes.
+ // Must be greater than 0, and less than or equal to 5TB (5120).
+ QuotaInGB int
+
+ MetaData map[string]string
+}
+
+// Create creates the specified Storage Share within the specified Storage Account
+func (client Client) Create(ctx context.Context, accountName, shareName string, input CreateInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "Create", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "Create", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "Create", "`shareName` must be a lower-cased string.")
+ }
+ if input.QuotaInGB <= 0 || input.QuotaInGB > 5120 {
+ return result, validation.NewError("shares.Client", "Create", "`input.QuotaInGB` must be greater than 0, and less than/equal to 5TB (5120 GB)")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("shares.Client", "Create", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.CreatePreparer(ctx, accountName, shareName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "Create", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "Create", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "Create", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreatePreparer prepares the Create request.
+func (client Client) CreatePreparer(ctx context.Context, accountName, shareName string, input CreateInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "share"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-share-quota": input.QuotaInGB,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSender sends the Create request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateResponder handles the response to the Create request. The method always
+// closes the http.Response Body.
+func (client Client) CreateResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/shares/delete.go b/storage/2017-07-29/file/shares/delete.go
new file mode 100644
index 0000000..70ef985
--- /dev/null
+++ b/storage/2017-07-29/file/shares/delete.go
@@ -0,0 +1,94 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete deletes the specified Storage Share from within a Storage Account
+func (client Client) Delete(ctx context.Context, accountName, shareName string, deleteSnapshots bool) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "Delete", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "Delete", "`shareName` must be a lower-cased string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, shareName, deleteSnapshots)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, shareName string, deleteSnapshots bool) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "share"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if deleteSnapshots {
+ headers["x-ms-delete-snapshots"] = "include"
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/shares/lifecycle_test.go b/storage/2017-07-29/file/shares/lifecycle_test.go
new file mode 100644
index 0000000..fbab96d
--- /dev/null
+++ b/storage/2017-07-29/file/shares/lifecycle_test.go
@@ -0,0 +1,152 @@
+package shares
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestSharesLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ input := CreateInput{
+ QuotaInGB: 1,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+
+ snapshot, err := sharesClient.CreateSnapshot(ctx, accountName, shareName, CreateSnapshotInput{})
+ if err != nil {
+ t.Fatalf("Error taking snapshot: %s", err)
+ }
+ t.Logf("Snapshot Date Time: %s", snapshot.SnapshotDateTime)
+
+ snapshotDetails, err := sharesClient.GetSnapshot(ctx, accountName, shareName, snapshot.SnapshotDateTime)
+ if err != nil {
+ t.Fatalf("Error retrieving snapshot: %s", err)
+ }
+
+ t.Logf("MetaData: %s", snapshotDetails.MetaData)
+
+ _, err = sharesClient.DeleteSnapshot(ctx, accountName, shareName, snapshot.SnapshotDateTime)
+ if err != nil {
+ t.Fatalf("Error deleting snapshot: %s", err)
+ }
+
+ stats, err := sharesClient.GetStats(ctx, accountName, shareName)
+ if err != nil {
+ t.Fatalf("Error retrieving stats: %s", err)
+ }
+
+ if stats.ShareUsageBytes != 0 {
+ t.Fatalf("Expected `stats.ShareUsageBytes` to be 0 but got: %d", stats.ShareUsageBytes)
+ }
+
+ share, err := sharesClient.GetProperties(ctx, accountName, shareName)
+ if err != nil {
+ t.Fatalf("Error retrieving share: %s", err)
+ }
+ if share.ShareQuota != 1 {
+ t.Fatalf("Expected Quota to be 1 but got: %d", share.ShareQuota)
+ }
+
+ _, err = sharesClient.SetProperties(ctx, accountName, shareName, 5)
+ if err != nil {
+ t.Fatalf("Error updating quota: %s", err)
+ }
+
+ share, err = sharesClient.GetProperties(ctx, accountName, shareName)
+ if err != nil {
+ t.Fatalf("Error retrieving share: %s", err)
+ }
+ if share.ShareQuota != 5 {
+ t.Fatalf("Expected Quota to be 5 but got: %d", share.ShareQuota)
+ }
+
+ updatedMetaData := map[string]string{
+ "hello": "world",
+ }
+ _, err = sharesClient.SetMetaData(ctx, accountName, shareName, updatedMetaData)
+ if err != nil {
+ t.Fatalf("Erorr setting metadata: %s", err)
+ }
+
+ result, err := sharesClient.GetMetaData(ctx, accountName, shareName)
+ if err != nil {
+ t.Fatalf("Error retrieving metadata: %s", err)
+ }
+
+ if result.MetaData["hello"] != "world" {
+ t.Fatalf("Expected metadata `hello` to be `world` but got: %q", result.MetaData["hello"])
+ }
+ if len(result.MetaData) != 1 {
+ t.Fatalf("Expected metadata to be 1 item but got: %s", result.MetaData)
+ }
+
+ acls, err := sharesClient.GetACL(ctx, accountName, shareName)
+ if err != nil {
+ t.Fatalf("Error retrieving ACL's: %s", err)
+ }
+ if len(acls.SignedIdentifiers) != 0 {
+ t.Fatalf("Expected 0 identifiers but got %d", len(acls.SignedIdentifiers))
+ }
+
+ updatedAcls := []SignedIdentifier{
+ {
+ Id: "abc123",
+ AccessPolicy: AccessPolicy{
+ Start: "2020-07-01T08:49:37.0000000Z",
+ Expiry: "2020-07-01T09:49:37.0000000Z",
+ Permission: "rwd",
+ },
+ },
+ {
+ Id: "bcd234",
+ AccessPolicy: AccessPolicy{
+ Start: "2020-07-01T08:49:37.0000000Z",
+ Expiry: "2020-07-01T09:49:37.0000000Z",
+ Permission: "rwd",
+ },
+ },
+ }
+ _, err = sharesClient.SetACL(ctx, accountName, shareName, updatedAcls)
+ if err != nil {
+ t.Fatalf("Error setting ACL's: %s", err)
+ }
+
+ acls, err = sharesClient.GetACL(ctx, accountName, shareName)
+ if err != nil {
+ t.Fatalf("Error retrieving ACL's: %s", err)
+ }
+ if len(acls.SignedIdentifiers) != 2 {
+ t.Fatalf("Expected 2 identifiers but got %d", len(acls.SignedIdentifiers))
+ }
+
+ _, err = sharesClient.Delete(ctx, accountName, shareName, false)
+ if err != nil {
+ t.Fatalf("Error deleting Share: %s", err)
+ }
+}
diff --git a/storage/2017-07-29/file/shares/metadata_get.go b/storage/2017-07-29/file/shares/metadata_get.go
new file mode 100644
index 0000000..9fa4d9f
--- /dev/null
+++ b/storage/2017-07-29/file/shares/metadata_get.go
@@ -0,0 +1,102 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetMetaDataResult struct {
+ autorest.Response
+
+ MetaData map[string]string
+}
+
+// GetMetaData returns the MetaData associated with the specified Storage Share
+func (client Client) GetMetaData(ctx context.Context, accountName, shareName string) (result GetMetaDataResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "GetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "GetMetaData", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "GetMetaData", "`shareName` must be a lower-cased string.")
+ }
+
+ req, err := client.GetMetaDataPreparer(ctx, accountName, shareName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetMetaDataSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetMetaDataPreparer prepares the GetMetaData request.
+func (client Client) GetMetaDataPreparer(ctx context.Context, accountName, shareName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetMetaDataSender sends the GetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetMetaDataResponder handles the response to the GetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) GetMetaDataResponder(resp *http.Response) (result GetMetaDataResult, err error) {
+ if resp.Header != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/shares/metadata_set.go b/storage/2017-07-29/file/shares/metadata_set.go
new file mode 100644
index 0000000..7e64e60
--- /dev/null
+++ b/storage/2017-07-29/file/shares/metadata_set.go
@@ -0,0 +1,97 @@
+package shares
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// SetMetaData sets the MetaData on the specified Storage Share
+func (client Client) SetMetaData(ctx context.Context, accountName, shareName string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "SetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "SetMetaData", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "SetMetaData", "`shareName` must be a lower-cased string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("shares.Client", "SetMetaData", fmt.Sprintf("`metadata` is not valid: %s.", err))
+ }
+
+ req, err := client.SetMetaDataPreparer(ctx, accountName, shareName, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetMetaDataSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetMetaDataPreparer prepares the SetMetaData request.
+func (client Client) SetMetaDataPreparer(ctx context.Context, accountName, shareName string, metaData map[string]string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetPropertiesSetMetaDataSender sends the SetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetMetaDataResponder handles the response to the SetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) SetMetaDataResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/shares/models.go b/storage/2017-07-29/file/shares/models.go
new file mode 100644
index 0000000..31ef7c2
--- /dev/null
+++ b/storage/2017-07-29/file/shares/models.go
@@ -0,0 +1,12 @@
+package shares
+
+type SignedIdentifier struct {
+ Id string `xml:"Id"`
+ AccessPolicy AccessPolicy `xml:"AccessPolicy"`
+}
+
+type AccessPolicy struct {
+ Start string `xml:"Start"`
+ Expiry string `xml:"Expiry"`
+ Permission string `xml:"Permission"`
+}
diff --git a/storage/2017-07-29/file/shares/properties_get.go b/storage/2017-07-29/file/shares/properties_get.go
new file mode 100644
index 0000000..80e26a4
--- /dev/null
+++ b/storage/2017-07-29/file/shares/properties_get.go
@@ -0,0 +1,111 @@
+package shares
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetPropertiesResult struct {
+ autorest.Response
+
+ MetaData map[string]string
+ ShareQuota int
+}
+
+// GetProperties returns the properties about the specified Storage Share
+func (client Client) GetProperties(ctx context.Context, accountName, shareName string) (result GetPropertiesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "GetProperties", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "GetProperties", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "GetProperties", "`shareName` must be a lower-cased string.")
+ }
+
+ req, err := client.GetPropertiesPreparer(ctx, accountName, shareName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetPropertiesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPropertiesPreparer prepares the GetProperties request.
+func (client Client) GetPropertiesPreparer(ctx context.Context, accountName, shareName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetPropertiesSender sends the GetProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetPropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetPropertiesResponder handles the response to the GetProperties request. The method always
+// closes the http.Response Body.
+func (client Client) GetPropertiesResponder(resp *http.Response) (result GetPropertiesResult, err error) {
+ if resp.Header != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+
+ quotaRaw := resp.Header.Get("x-ms-share-quota")
+ quota, e := strconv.Atoi(quotaRaw)
+ if e != nil {
+ return result, fmt.Errorf("Error converting %q to an integer: %s", quotaRaw, err)
+ }
+ result.ShareQuota = quota
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/shares/properties_set.go b/storage/2017-07-29/file/shares/properties_set.go
new file mode 100644
index 0000000..4553e5e
--- /dev/null
+++ b/storage/2017-07-29/file/shares/properties_set.go
@@ -0,0 +1,95 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// SetProperties lets you update the Quota for the specified Storage Share
+func (client Client) SetProperties(ctx context.Context, accountName, shareName string, newQuotaGB int) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "SetProperties", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "SetProperties", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "SetProperties", "`shareName` must be a lower-cased string.")
+ }
+ if newQuotaGB <= 0 || newQuotaGB > 5120 {
+ return result, validation.NewError("shares.Client", "SetProperties", "`newQuotaGB` must be greater than 0, and less than/equal to 5TB (5120 GB)")
+ }
+
+ req, err := client.SetPropertiesPreparer(ctx, accountName, shareName, newQuotaGB)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetPropertiesSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetPropertiesPreparer prepares the SetProperties request.
+func (client Client) SetPropertiesPreparer(ctx context.Context, accountName, shareName string, quotaGB int) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "comp": autorest.Encode("query", "properties"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-share-quota": quotaGB,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetPropertiesSender sends the SetProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetPropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetPropertiesResponder handles the response to the SetProperties request. The method always
+// closes the http.Response Body.
+func (client Client) SetPropertiesResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/shares/resource_id.go b/storage/2017-07-29/file/shares/resource_id.go
new file mode 100644
index 0000000..bfdcbfd
--- /dev/null
+++ b/storage/2017-07-29/file/shares/resource_id.go
@@ -0,0 +1,46 @@
+package shares
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given File Share
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, shareName string) string {
+ domain := endpoints.GetFileEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s", domain, shareName)
+}
+
+type ResourceID struct {
+ AccountName string
+ ShareName string
+}
+
+// ParseResourceID parses the specified Resource ID and returns an object
+// which can be used to interact with the Storage Shares SDK
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://foo.file.core.windows.net/Bar
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ shareName := strings.TrimPrefix(uri.Path, "/")
+ return &ResourceID{
+ AccountName: *accountName,
+ ShareName: shareName,
+ }, nil
+}
diff --git a/storage/2017-07-29/file/shares/resource_id_test.go b/storage/2017-07-29/file/shares/resource_id_test.go
new file mode 100644
index 0000000..1b7eea3
--- /dev/null
+++ b/storage/2017-07-29/file/shares/resource_id_test.go
@@ -0,0 +1,79 @@
+package shares
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.file.core.chinacloudapi.cn/share1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.file.core.cloudapi.de/share1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.file.core.windows.net/share1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.file.core.usgovcloudapi.net/share1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "share1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.file.core.chinacloudapi.cn/share1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.file.core.cloudapi.de/share1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.file.core.windows.net/share1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.file.core.usgovcloudapi.net/share1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected the account name to be `account1` but got %q", actual.AccountName)
+ }
+
+ if actual.ShareName != "share1" {
+ t.Fatalf("Expected the share name to be `share1` but got %q", actual.ShareName)
+ }
+ }
+}
diff --git a/storage/2017-07-29/file/shares/snapshot_create.go b/storage/2017-07-29/file/shares/snapshot_create.go
new file mode 100644
index 0000000..0ded38b
--- /dev/null
+++ b/storage/2017-07-29/file/shares/snapshot_create.go
@@ -0,0 +1,115 @@
+package shares
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type CreateSnapshotInput struct {
+ MetaData map[string]string
+}
+
+type CreateSnapshotResult struct {
+ autorest.Response
+
+ // This header is a DateTime value that uniquely identifies the share snapshot.
+ // The value of this header may be used in subsequent requests to access the share snapshot.
+ // This value is opaque.
+ SnapshotDateTime string
+}
+
+// CreateSnapshot creates a read-only snapshot of the share
+// A share can support creation of 200 share snapshots. Attempting to create more than 200 share snapshots fails with 409 (Conflict).
+// Attempting to create a share snapshot while a previous Snapshot Share operation is in progress fails with 409 (Conflict).
+func (client Client) CreateSnapshot(ctx context.Context, accountName, shareName string, input CreateSnapshotInput) (result CreateSnapshotResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "CreateSnapshot", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "CreateSnapshot", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "CreateSnapshot", "`shareName` must be a lower-cased string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("shares.Client", "CreateSnapshot", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.CreateSnapshotPreparer(ctx, accountName, shareName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "CreateSnapshot", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSnapshotSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "CreateSnapshot", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateSnapshotResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "CreateSnapshot", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreateSnapshotPreparer prepares the CreateSnapshot request.
+func (client Client) CreateSnapshotPreparer(ctx context.Context, accountName, shareName string, input CreateSnapshotInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "snapshot"),
+ "restype": autorest.Encode("query", "share"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSnapshotSender sends the CreateSnapshot request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSnapshotSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateSnapshotResponder handles the response to the CreateSnapshot request. The method always
+// closes the http.Response Body.
+func (client Client) CreateSnapshotResponder(resp *http.Response) (result CreateSnapshotResult, err error) {
+ result.SnapshotDateTime = resp.Header.Get("x-ms-snapshot")
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/shares/snapshot_delete.go b/storage/2017-07-29/file/shares/snapshot_delete.go
new file mode 100644
index 0000000..1f5d665
--- /dev/null
+++ b/storage/2017-07-29/file/shares/snapshot_delete.go
@@ -0,0 +1,94 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// DeleteSnapshot deletes the specified Snapshot of a Storage Share
+func (client Client) DeleteSnapshot(ctx context.Context, accountName, shareName string, shareSnapshot string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "DeleteSnapshot", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "DeleteSnapshot", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "DeleteSnapshot", "`shareName` must be a lower-cased string.")
+ }
+ if shareSnapshot == "" {
+ return result, validation.NewError("shares.Client", "DeleteSnapshot", "`shareSnapshot` cannot be an empty string.")
+ }
+
+ req, err := client.DeleteSnapshotPreparer(ctx, accountName, shareName, shareSnapshot)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "DeleteSnapshot", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSnapshotSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "DeleteSnapshot", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteSnapshotResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "DeleteSnapshot", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeleteSnapshotPreparer prepares the DeleteSnapshot request.
+func (client Client) DeleteSnapshotPreparer(ctx context.Context, accountName, shareName string, shareSnapshot string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "share"),
+ "sharesnapshot": autorest.Encode("query", shareSnapshot),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSnapshotSender sends the DeleteSnapshot request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSnapshotSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteSnapshotResponder handles the response to the DeleteSnapshot request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteSnapshotResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/shares/snapshot_get.go b/storage/2017-07-29/file/shares/snapshot_get.go
new file mode 100644
index 0000000..2cf5f16
--- /dev/null
+++ b/storage/2017-07-29/file/shares/snapshot_get.go
@@ -0,0 +1,105 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetSnapshotPropertiesResult struct {
+ autorest.Response
+
+ MetaData map[string]string
+}
+
+// GetSnapshot gets information about the specified Snapshot of the specified Storage Share
+func (client Client) GetSnapshot(ctx context.Context, accountName, shareName, snapshotShare string) (result GetSnapshotPropertiesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "GetSnapshot", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "GetSnapshot", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "GetSnapshot", "`shareName` must be a lower-cased string.")
+ }
+ if snapshotShare == "" {
+ return result, validation.NewError("shares.Client", "GetSnapshot", "`snapshotShare` cannot be an empty string.")
+ }
+
+ req, err := client.GetSnapshotPreparer(ctx, accountName, shareName, snapshotShare)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetSnapshot", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetSnapshotSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetSnapshot", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetSnapshotResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetSnapshot", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetSnapshotPreparer prepares the GetSnapshot request.
+func (client Client) GetSnapshotPreparer(ctx context.Context, accountName, shareName, snapshotShare string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "snapshot": autorest.Encode("query", snapshotShare),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetSnapshotSender sends the GetSnapshot request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetSnapshotSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetSnapshotResponder handles the response to the GetSnapshot request. The method always
+// closes the http.Response Body.
+func (client Client) GetSnapshotResponder(resp *http.Response) (result GetSnapshotPropertiesResult, err error) {
+ if resp.Header != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/shares/stats.go b/storage/2017-07-29/file/shares/stats.go
new file mode 100644
index 0000000..3539ecc
--- /dev/null
+++ b/storage/2017-07-29/file/shares/stats.go
@@ -0,0 +1,100 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetStatsResult struct {
+ autorest.Response
+
+ // The approximate size of the data stored on the share.
+ // Note that this value may not include all recently created or recently resized files.
+ ShareUsageBytes int64 `xml:"ShareUsageBytes"`
+}
+
+// GetStats returns information about the specified Storage Share
+func (client Client) GetStats(ctx context.Context, accountName, shareName string) (result GetStatsResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "GetStats", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "GetStats", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "GetStats", "`shareName` must be a lower-cased string.")
+ }
+
+ req, err := client.GetStatsPreparer(ctx, accountName, shareName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetStats", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetStatsSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetStats", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetStatsResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetStats", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetStatsPreparer prepares the GetStats request.
+func (client Client) GetStatsPreparer(ctx context.Context, accountName, shareName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "comp": autorest.Encode("query", "stats"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetStatsSender sends the GetStats request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetStatsSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetStatsResponder handles the response to the GetStats request. The method always
+// closes the http.Response Body.
+func (client Client) GetStatsResponder(resp *http.Response) (result GetStatsResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/file/shares/version.go b/storage/2017-07-29/file/shares/version.go
new file mode 100644
index 0000000..f9d82d9
--- /dev/null
+++ b/storage/2017-07-29/file/shares/version.go
@@ -0,0 +1,14 @@
+package shares
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2017-07-29"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2017-07-29/queue/messages/README.md b/storage/2017-07-29/queue/messages/README.md
new file mode 100644
index 0000000..2a75b54
--- /dev/null
+++ b/storage/2017-07-29/queue/messages/README.md
@@ -0,0 +1,42 @@
+## Queue Storage Messages SDK for API version 2017-07-29
+
+This package allows you to interact with the Messages Queue Storage API
+
+### Supported Authorizers
+
+* SharedKeyLite (Blob, File & Queue)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/queue/messages"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ queueName := "myqueue"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ messagesClient := messages.New()
+ messagesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ input := messages.PutInput{
+ Message: "hello",
+ }
+ if _, err := messagesClient.Put(ctx, accountName, queueName, input); err != nil {
+ return fmt.Errorf("Error creating Message: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2017-07-29/queue/messages/client.go b/storage/2017-07-29/queue/messages/client.go
new file mode 100644
index 0000000..08b1801
--- /dev/null
+++ b/storage/2017-07-29/queue/messages/client.go
@@ -0,0 +1,25 @@
+package messages
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for Messages.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2017-07-29/queue/messages/delete.go b/storage/2017-07-29/queue/messages/delete.go
new file mode 100644
index 0000000..1ec0e1a
--- /dev/null
+++ b/storage/2017-07-29/queue/messages/delete.go
@@ -0,0 +1,97 @@
+package messages
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete deletes a specific message
+func (client Client) Delete(ctx context.Context, accountName, queueName, messageID, popReceipt string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("messages.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("messages.Client", "Delete", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("messages.Client", "Delete", "`queueName` must be a lower-cased string.")
+ }
+ if messageID == "" {
+ return result, validation.NewError("messages.Client", "Delete", "`messageID` cannot be an empty string.")
+ }
+ if popReceipt == "" {
+ return result, validation.NewError("messages.Client", "Delete", "`popReceipt` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, queueName, messageID, popReceipt)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "messages.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, queueName, messageID, popReceipt string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ "messageID": autorest.Encode("path", messageID),
+ }
+
+ queryParameters := map[string]interface{}{
+ "popreceipt": autorest.Encode("query", popReceipt),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}/messages/{messageID}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/queue/messages/get.go b/storage/2017-07-29/queue/messages/get.go
new file mode 100644
index 0000000..4edeb6d
--- /dev/null
+++ b/storage/2017-07-29/queue/messages/get.go
@@ -0,0 +1,112 @@
+package messages
+
+import (
+ "context"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetInput struct {
+ // VisibilityTimeout specifies the new visibility timeout value, in seconds, relative to server time.
+ // The new value must be larger than or equal to 0, and cannot be larger than 7 days.
+ VisibilityTimeout *int
+}
+
+// Get retrieves one or more messages from the front of the queue
+func (client Client) Get(ctx context.Context, accountName, queueName string, numberOfMessages int, input GetInput) (result QueueMessagesListResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("messages.Client", "Get", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("messages.Client", "Get", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("messages.Client", "Get", "`queueName` must be a lower-cased string.")
+ }
+ if numberOfMessages < 1 || numberOfMessages > 32 {
+ return result, validation.NewError("messages.Client", "Get", "`numberOfMessages` must be between 1 and 32.")
+ }
+ if input.VisibilityTimeout != nil {
+ t := *input.VisibilityTimeout
+ maxTime := (time.Hour * 24 * 7).Seconds()
+ if t < 1 || t < int(maxTime) {
+ return result, validation.NewError("messages.Client", "Get", "`input.VisibilityTimeout` must be larger than or equal to 1 second, and cannot be larger than 7 days.")
+ }
+ }
+
+ req, err := client.GetPreparer(ctx, accountName, queueName, numberOfMessages, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Get", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "messages.Client", "Get", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Get", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPreparer prepares the Get request.
+func (client Client) GetPreparer(ctx context.Context, accountName, queueName string, numberOfMessages int, input GetInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "numofmessages": autorest.Encode("query", numberOfMessages),
+ }
+
+ if input.VisibilityTimeout != nil {
+ queryParameters["visibilitytimeout"] = autorest.Encode("query", *input.VisibilityTimeout)
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}/messages", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetSender sends the Get request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetResponder handles the response to the Get request. The method always
+// closes the http.Response Body.
+func (client Client) GetResponder(resp *http.Response) (result QueueMessagesListResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ autorest.ByUnmarshallingXML(&result),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/queue/messages/lifecycle_test.go b/storage/2017-07-29/queue/messages/lifecycle_test.go
new file mode 100644
index 0000000..4093d9f
--- /dev/null
+++ b/storage/2017-07-29/queue/messages/lifecycle_test.go
@@ -0,0 +1,95 @@
+package messages
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/queue/queues"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestLifeCycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ queueName := fmt.Sprintf("queue-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ queuesClient := queues.NewWithEnvironment(client.Environment)
+ queuesClient.Client = client.PrepareWithAuthorizer(queuesClient.Client, storageAuth)
+
+ messagesClient := NewWithEnvironment(client.Environment)
+ messagesClient.Client = client.PrepareWithAuthorizer(messagesClient.Client, storageAuth)
+
+ _, err = queuesClient.Create(ctx, accountName, queueName, map[string]string{})
+ if err != nil {
+ t.Fatalf("Error creating queue: %s", err)
+ }
+ defer queuesClient.Delete(ctx, accountName, queueName)
+
+ input := PutInput{
+ Message: "ohhai",
+ }
+ putResp, err := messagesClient.Put(ctx, accountName, queueName, input)
+ if err != nil {
+ t.Fatalf("Error putting message in queue: %s", err)
+ }
+
+ messageId := (*putResp.QueueMessages)[0].MessageId
+ popReceipt := (*putResp.QueueMessages)[0].PopReceipt
+
+ _, err = messagesClient.Update(ctx, accountName, queueName, messageId, UpdateInput{
+ PopReceipt: popReceipt,
+ Message: "Updated message",
+ VisibilityTimeout: 65,
+ })
+ if err != nil {
+ t.Fatalf("Error updating: %s", err)
+ }
+
+ for i := 0; i < 5; i++ {
+ input := PutInput{
+ Message: fmt.Sprintf("Message %d", i),
+ }
+ _, err := messagesClient.Put(ctx, accountName, queueName, input)
+ if err != nil {
+ t.Fatalf("Error putting message %d in queue: %s", i, err)
+ }
+ }
+
+ peakedMessages, err := messagesClient.Peek(ctx, accountName, queueName, 3)
+ if err != nil {
+ t.Fatalf("Error peaking messages: %s", err)
+ }
+
+ for _, v := range *peakedMessages.QueueMessages {
+ t.Logf("Message: %q", v.MessageId)
+ }
+
+ retrievedMessages, err := messagesClient.Get(ctx, accountName, queueName, 6, GetInput{})
+ if err != nil {
+ t.Fatalf("Error retrieving messages: %s", err)
+ }
+
+ for _, v := range *retrievedMessages.QueueMessages {
+ t.Logf("Message: %q", v.MessageId)
+
+ _, err = messagesClient.Delete(ctx, accountName, queueName, v.MessageId, v.PopReceipt)
+ if err != nil {
+ t.Fatalf("Error deleting message from queue: %s", err)
+ }
+ }
+}
diff --git a/storage/2017-07-29/queue/messages/models.go b/storage/2017-07-29/queue/messages/models.go
new file mode 100644
index 0000000..67815a8
--- /dev/null
+++ b/storage/2017-07-29/queue/messages/models.go
@@ -0,0 +1,21 @@
+package messages
+
+import "github.com/Azure/go-autorest/autorest"
+
+type QueueMessage struct {
+ MessageText string `xml:"MessageText"`
+}
+
+type QueueMessagesListResult struct {
+ autorest.Response
+
+ QueueMessages *[]QueueMessageResponse `xml:"QueueMessage"`
+}
+
+type QueueMessageResponse struct {
+ MessageId string `xml:"MessageId"`
+ InsertionTime string `xml:"InsertionTime"`
+ ExpirationTime string `xml:"ExpirationTime"`
+ PopReceipt string `xml:"PopReceipt"`
+ TimeNextVisible string `xml:"TimeNextVisible"`
+}
diff --git a/storage/2017-07-29/queue/messages/peek.go b/storage/2017-07-29/queue/messages/peek.go
new file mode 100644
index 0000000..7288bd5
--- /dev/null
+++ b/storage/2017-07-29/queue/messages/peek.go
@@ -0,0 +1,95 @@
+package messages
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Peek retrieves one or more messages from the front of the queue, but doesn't alter the visibility of the messages
+func (client Client) Peek(ctx context.Context, accountName, queueName string, numberOfMessages int) (result QueueMessagesListResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("messages.Client", "Peek", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("messages.Client", "Peek", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("messages.Client", "Peek", "`queueName` must be a lower-cased string.")
+ }
+ if numberOfMessages < 1 || numberOfMessages > 32 {
+ return result, validation.NewError("messages.Client", "Peek", "`numberOfMessages` must be between 1 and 32.")
+ }
+
+ req, err := client.PeekPreparer(ctx, accountName, queueName, numberOfMessages)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Peek", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PeekSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "messages.Client", "Peek", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PeekResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Peek", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PeekPreparer prepares the Peek request.
+func (client Client) PeekPreparer(ctx context.Context, accountName, queueName string, numberOfMessages int) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "numofmessages": autorest.Encode("query", numberOfMessages),
+ "peekonly": autorest.Encode("query", true),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}/messages", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PeekSender sends the Peek request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PeekSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PeekResponder handles the response to the Peek request. The method always
+// closes the http.Response Body.
+func (client Client) PeekResponder(resp *http.Response) (result QueueMessagesListResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ autorest.ByUnmarshallingXML(&result),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/queue/messages/put.go b/storage/2017-07-29/queue/messages/put.go
new file mode 100644
index 0000000..612b4a1
--- /dev/null
+++ b/storage/2017-07-29/queue/messages/put.go
@@ -0,0 +1,120 @@
+package messages
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type PutInput struct {
+ // A message must be in a format that can be included in an XML request with UTF-8 encoding.
+ // The encoded message can be up to 64 KB in size.
+ Message string
+
+ // The maximum time-to-live can be any positive number,
+ // as well as -1 indicating that the message does not expire.
+ // If this parameter is omitted, the default time-to-live is 7 days.
+ MessageTtl *int
+
+ // Specifies the new visibility timeout value, in seconds, relative to server time.
+ // The new value must be larger than or equal to 0, and cannot be larger than 7 days.
+ // The visibility timeout of a message cannot be set to a value later than the expiry time.
+ // visibilitytimeout should be set to a value smaller than the time-to-live value.
+ // If not specified, the default value is 0.
+ VisibilityTimeout *int
+}
+
+// Put adds a new message to the back of the message queue
+func (client Client) Put(ctx context.Context, accountName, queueName string, input PutInput) (result QueueMessagesListResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("messages.Client", "Put", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("messages.Client", "Put", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("messages.Client", "Put", "`queueName` must be a lower-cased string.")
+ }
+
+ req, err := client.PutPreparer(ctx, accountName, queueName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Put", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "messages.Client", "Put", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Put", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutPreparer prepares the Put request.
+func (client Client) PutPreparer(ctx context.Context, accountName, queueName string, input PutInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ queryParameters := map[string]interface{}{}
+
+ if input.MessageTtl != nil {
+ queryParameters["messagettl"] = autorest.Encode("path", *input.MessageTtl)
+ }
+
+ if input.VisibilityTimeout != nil {
+ queryParameters["visibilitytimeout"] = autorest.Encode("path", *input.VisibilityTimeout)
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ body := QueueMessage{
+ MessageText: input.Message,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPost(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}/messages", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithXML(body),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutSender sends the Put request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutResponder handles the response to the Put request. The method always
+// closes the http.Response Body.
+func (client Client) PutResponder(resp *http.Response) (result QueueMessagesListResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ autorest.ByUnmarshallingXML(&result),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/queue/messages/resource_id.go b/storage/2017-07-29/queue/messages/resource_id.go
new file mode 100644
index 0000000..7ece98a
--- /dev/null
+++ b/storage/2017-07-29/queue/messages/resource_id.go
@@ -0,0 +1,56 @@
+package messages
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Message within a Queue
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, queueName, messageID string) string {
+ domain := endpoints.GetQueueEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s/messages/%s", domain, queueName, messageID)
+}
+
+type ResourceID struct {
+ AccountName string
+ QueueName string
+ MessageID string
+}
+
+// ParseResourceID parses the specified Resource ID and returns an object
+// which can be used to interact with the Message within a Queue
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://account1.queue.core.chinacloudapi.cn/queue1/messages/message1
+
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ path := strings.TrimPrefix(uri.Path, "/")
+ segments := strings.Split(path, "/")
+ if len(segments) != 3 {
+ return nil, fmt.Errorf("Expected the path to contain 3 segments but got %d", len(segments))
+ }
+
+ queueName := segments[0]
+ messageID := segments[2]
+ return &ResourceID{
+ AccountName: *accountName,
+ MessageID: messageID,
+ QueueName: queueName,
+ }, nil
+}
diff --git a/storage/2017-07-29/queue/messages/resource_id_test.go b/storage/2017-07-29/queue/messages/resource_id_test.go
new file mode 100644
index 0000000..5053279
--- /dev/null
+++ b/storage/2017-07-29/queue/messages/resource_id_test.go
@@ -0,0 +1,81 @@
+package messages
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.queue.core.chinacloudapi.cn/queue1/messages/message1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.queue.core.cloudapi.de/queue1/messages/message1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.queue.core.windows.net/queue1/messages/message1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.queue.core.usgovcloudapi.net/queue1/messages/message1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "queue1", "message1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.queue.core.chinacloudapi.cn/queue1/messages/message1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.queue.core.cloudapi.de/queue1/messages/message1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.queue.core.windows.net/queue1/messages/message1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.queue.core.usgovcloudapi.net/queue1/messages/message1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.QueueName != "queue1" {
+ t.Fatalf("Expected Queue Name to be `queue1` but got %q", actual.QueueName)
+ }
+ if actual.MessageID != "message1" {
+ t.Fatalf("Expected Message ID to be `message1` but got %q", actual.MessageID)
+ }
+ }
+}
diff --git a/storage/2017-07-29/queue/messages/update.go b/storage/2017-07-29/queue/messages/update.go
new file mode 100644
index 0000000..fb10fad
--- /dev/null
+++ b/storage/2017-07-29/queue/messages/update.go
@@ -0,0 +1,115 @@
+package messages
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type UpdateInput struct {
+ // A message must be in a format that can be included in an XML request with UTF-8 encoding.
+ // The encoded message can be up to 64 KB in size.
+ Message string
+
+ // Specifies the valid pop receipt value required to modify this message.
+ PopReceipt string
+
+ // Specifies the new visibility timeout value, in seconds, relative to server time.
+ // The new value must be larger than or equal to 0, and cannot be larger than 7 days.
+ // The visibility timeout of a message cannot be set to a value later than the expiry time.
+ // A message can be updated until it has been deleted or has expired.
+ VisibilityTimeout int
+}
+
+// Update updates an existing message based on it's Pop Receipt
+func (client Client) Update(ctx context.Context, accountName, queueName string, messageID string, input UpdateInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("messages.Client", "Update", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("messages.Client", "Update", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("messages.Client", "Update", "`queueName` must be a lower-cased string.")
+ }
+ if input.PopReceipt == "" {
+ return result, validation.NewError("messages.Client", "Update", "`input.PopReceipt` cannot be an empty string.")
+ }
+
+ req, err := client.UpdatePreparer(ctx, accountName, queueName, messageID, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Update", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.UpdateSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "messages.Client", "Update", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.UpdateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Update", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// UpdatePreparer prepares the Update request.
+func (client Client) UpdatePreparer(ctx context.Context, accountName, queueName string, messageID string, input UpdateInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ "messageID": autorest.Encode("path", messageID),
+ }
+
+ queryParameters := map[string]interface{}{
+ "popreceipt": autorest.Encode("query", input.PopReceipt),
+ "visibilitytimeout": autorest.Encode("query", input.VisibilityTimeout),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ body := QueueMessage{
+ MessageText: input.Message,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}/messages/{messageID}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithXML(body),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// UpdateSender sends the Update request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) UpdateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// UpdateResponder handles the response to the Update request. The method always
+// closes the http.Response Body.
+func (client Client) UpdateResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/queue/messages/version.go b/storage/2017-07-29/queue/messages/version.go
new file mode 100644
index 0000000..53be1c0
--- /dev/null
+++ b/storage/2017-07-29/queue/messages/version.go
@@ -0,0 +1,14 @@
+package messages
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2017-07-29"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2017-07-29/queue/queues/README.md b/storage/2017-07-29/queue/queues/README.md
new file mode 100644
index 0000000..fe85b8a
--- /dev/null
+++ b/storage/2017-07-29/queue/queues/README.md
@@ -0,0 +1,42 @@
+## Queue Storage Queues SDK for API version 2017-07-29
+
+This package allows you to interact with the Queues Queue Storage API
+
+### Supported Authorizers
+
+* SharedKeyLite (Blob, File & Queue)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/queue/queues"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ queueName := "myqueue"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ queuesClient := queues.New()
+ queuesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ metadata := map[string]string{
+ "hello": "world",
+ }
+ if _, err := queuesClient.Create(ctx, accountName, queueName, metadata); err != nil {
+ return fmt.Errorf("Error creating Queue: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2017-07-29/queue/queues/client.go b/storage/2017-07-29/queue/queues/client.go
new file mode 100644
index 0000000..2f80085
--- /dev/null
+++ b/storage/2017-07-29/queue/queues/client.go
@@ -0,0 +1,25 @@
+package queues
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for Queue Storage Shares.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2017-07-29/queue/queues/create.go b/storage/2017-07-29/queue/queues/create.go
new file mode 100644
index 0000000..f18910a
--- /dev/null
+++ b/storage/2017-07-29/queue/queues/create.go
@@ -0,0 +1,92 @@
+package queues
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// Create creates the specified Queue within the specified Storage Account
+func (client Client) Create(ctx context.Context, accountName, queueName string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("queues.Client", "Create", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("queues.Client", "Create", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("queues.Client", "Create", "`queueName` must be a lower-cased string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("queues.Client", "Create", fmt.Sprintf("`metadata` is not valid: %s.", err))
+ }
+
+ req, err := client.CreatePreparer(ctx, accountName, queueName, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "Create", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "queues.Client", "Create", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "Create", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreatePreparer prepares the Create request.
+func (client Client) CreatePreparer(ctx context.Context, accountName string, queueName string, metaData map[string]string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSender sends the Create request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateResponder handles the response to the Create request. The method always
+// closes the http.Response Body.
+func (client Client) CreateResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/queue/queues/delete.go b/storage/2017-07-29/queue/queues/delete.go
new file mode 100644
index 0000000..5f70595
--- /dev/null
+++ b/storage/2017-07-29/queue/queues/delete.go
@@ -0,0 +1,85 @@
+package queues
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete deletes the specified Queue within the specified Storage Account
+func (client Client) Delete(ctx context.Context, accountName, queueName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("queues.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("queues.Client", "Delete", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("queues.Client", "Delete", "`queueName` must be a lower-cased string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, queueName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "queues.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName string, queueName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/queue/queues/lifecycle_test.go b/storage/2017-07-29/queue/queues/lifecycle_test.go
new file mode 100644
index 0000000..d24245c
--- /dev/null
+++ b/storage/2017-07-29/queue/queues/lifecycle_test.go
@@ -0,0 +1,93 @@
+package queues
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestQueuesLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ queueName := fmt.Sprintf("queue-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ queuesClient := NewWithEnvironment(client.Environment)
+ queuesClient.Client = client.PrepareWithAuthorizer(queuesClient.Client, storageAuth)
+
+ // first let's test an empty container
+ _, err = queuesClient.Create(ctx, accountName, queueName, map[string]string{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+
+ // then let's retrieve it to ensure there's no metadata..
+ resp, err := queuesClient.GetMetaData(ctx, accountName, queueName)
+ if err != nil {
+ t.Fatalf("Error retrieving MetaData: %s", err)
+ }
+ if len(resp.MetaData) != 0 {
+ t.Fatalf("Expected no MetaData but got: %s", err)
+ }
+
+ // then let's add some..
+ updatedMetaData := map[string]string{
+ "band": "panic",
+ "boots": "the-overpass",
+ }
+ _, err = queuesClient.SetMetaData(ctx, accountName, queueName, updatedMetaData)
+ if err != nil {
+ t.Fatalf("Error setting MetaData: %s", err)
+ }
+
+ resp, err = queuesClient.GetMetaData(ctx, accountName, queueName)
+ if err != nil {
+ t.Fatalf("Error re-retrieving MetaData: %s", err)
+ }
+
+ if len(resp.MetaData) != 2 {
+ t.Fatalf("Expected metadata to have 2 items but got: %s", resp.MetaData)
+ }
+ if resp.MetaData["band"] != "panic" {
+ t.Fatalf("Expected `band` to be `panic` but got: %s", resp.MetaData["band"])
+ }
+ if resp.MetaData["boots"] != "the-overpass" {
+ t.Fatalf("Expected `boots` to be `the-overpass` but got: %s", resp.MetaData["boots"])
+ }
+
+ // and woo let's remove it again
+ _, err = queuesClient.SetMetaData(ctx, accountName, queueName, map[string]string{})
+ if err != nil {
+ t.Fatalf("Error setting MetaData: %s", err)
+ }
+
+ resp, err = queuesClient.GetMetaData(ctx, accountName, queueName)
+ if err != nil {
+ t.Fatalf("Error retrieving MetaData: %s", err)
+ }
+ if len(resp.MetaData) != 0 {
+ t.Fatalf("Expected no MetaData but got: %s", err)
+ }
+
+ log.Printf("[DEBUG] Deleting..")
+ _, err = queuesClient.Delete(ctx, accountName, queueName)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error deleting: %s", err))
+ }
+}
diff --git a/storage/2017-07-29/queue/queues/metadata_get.go b/storage/2017-07-29/queue/queues/metadata_get.go
new file mode 100644
index 0000000..9c230b6
--- /dev/null
+++ b/storage/2017-07-29/queue/queues/metadata_get.go
@@ -0,0 +1,101 @@
+package queues
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetMetaDataResult struct {
+ autorest.Response
+
+ MetaData map[string]string
+}
+
+// GetMetaData returns the metadata for this Queue
+func (client Client) GetMetaData(ctx context.Context, accountName, queueName string) (result GetMetaDataResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("queues.Client", "GetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("queues.Client", "GetMetaData", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("queues.Client", "GetMetaData", "`queueName` must be a lower-cased string.")
+ }
+
+ req, err := client.GetMetaDataPreparer(ctx, accountName, queueName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "GetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetMetaDataSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "queues.Client", "GetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "GetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetMetaDataPreparer prepares the GetMetaData request.
+func (client Client) GetMetaDataPreparer(ctx context.Context, accountName, queueName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetMetaDataSender sends the GetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetMetaDataResponder handles the response to the GetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) GetMetaDataResponder(resp *http.Response) (result GetMetaDataResult, err error) {
+ if resp != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/queue/queues/metadata_set.go b/storage/2017-07-29/queue/queues/metadata_set.go
new file mode 100644
index 0000000..51154a5
--- /dev/null
+++ b/storage/2017-07-29/queue/queues/metadata_set.go
@@ -0,0 +1,97 @@
+package queues
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// SetMetaData returns the metadata for this Queue
+func (client Client) SetMetaData(ctx context.Context, accountName, queueName string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("queues.Client", "SetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("queues.Client", "SetMetaData", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("queues.Client", "SetMetaData", "`queueName` must be a lower-cased string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("queues.Client", "SetMetaData", fmt.Sprintf("`metadata` is not valid: %s.", err))
+ }
+
+ req, err := client.SetMetaDataPreparer(ctx, accountName, queueName, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetMetaDataSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetMetaDataPreparer prepares the SetMetaData request.
+func (client Client) SetMetaDataPreparer(ctx context.Context, accountName, queueName string, metaData map[string]string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetMetaDataSender sends the SetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetMetaDataResponder handles the response to the SetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) SetMetaDataResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/queue/queues/models.go b/storage/2017-07-29/queue/queues/models.go
new file mode 100644
index 0000000..89c2380
--- /dev/null
+++ b/storage/2017-07-29/queue/queues/models.go
@@ -0,0 +1,42 @@
+package queues
+
+type StorageServiceProperties struct {
+ Logging *LoggingConfig `xml:"Logging,omitempty"`
+ HourMetrics *MetricsConfig `xml:"HourMetrics,omitempty"`
+ MinuteMetrics *MetricsConfig `xml:"MinuteMetrics,omitempty"`
+ Cors *Cors `xml:"Cors,omitempty"`
+}
+
+type LoggingConfig struct {
+ Version string `xml:"Version"`
+ Delete bool `xml:"Delete"`
+ Read bool `xml:"Read"`
+ Write bool `xml:"Write"`
+ RetentionPolicy RetentionPolicy `xml:"RetentionPolicy"`
+}
+
+type MetricsConfig struct {
+ Version string `xml:"Version"`
+ Enabled bool `xml:"Enabled"`
+ RetentionPolicy RetentionPolicy `xml:"RetentionPolicy"`
+
+ // Element IncludeAPIs is only expected when Metrics is enabled
+ IncludeAPIs *bool `xml:"IncludeAPIs,omitempty"`
+}
+
+type RetentionPolicy struct {
+ Enabled bool `xml:"Enabled"`
+ Days int `xml:"Days"`
+}
+
+type Cors struct {
+ CorsRule CorsRule `xml:"CorsRule"`
+}
+
+type CorsRule struct {
+ AllowedOrigins string `xml:"AllowedOrigins"`
+ AllowedMethods string `xml:"AllowedMethods"`
+ AllowedHeaders string `xml:"AllowedHeaders`
+ ExposedHeaders string `xml:"ExposedHeaders"`
+ MaxAgeInSeconds int `xml:"MaxAgeInSeconds"`
+}
diff --git a/storage/2017-07-29/queue/queues/resource_id.go b/storage/2017-07-29/queue/queues/resource_id.go
new file mode 100644
index 0000000..ee28b8b
--- /dev/null
+++ b/storage/2017-07-29/queue/queues/resource_id.go
@@ -0,0 +1,46 @@
+package queues
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Queue
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, queueName string) string {
+ domain := endpoints.GetQueueEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s", domain, queueName)
+}
+
+type ResourceID struct {
+ AccountName string
+ QueueName string
+}
+
+// ParseResourceID parses the Resource ID and returns an Object which
+// can be used to interact with a Queue within a Storage Account
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://foo.queue.core.windows.net/Bar
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ queueName := strings.TrimPrefix(uri.Path, "/")
+ return &ResourceID{
+ AccountName: *accountName,
+ QueueName: queueName,
+ }, nil
+}
diff --git a/storage/2017-07-29/queue/queues/resource_id_test.go b/storage/2017-07-29/queue/queues/resource_id_test.go
new file mode 100644
index 0000000..89323d7
--- /dev/null
+++ b/storage/2017-07-29/queue/queues/resource_id_test.go
@@ -0,0 +1,79 @@
+package queues
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.queue.core.chinacloudapi.cn/queue1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.queue.core.cloudapi.de/queue1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.queue.core.windows.net/queue1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.queue.core.usgovcloudapi.net/queue1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "queue1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.queue.core.chinacloudapi.cn/queue1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.queue.core.cloudapi.de/queue1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.queue.core.windows.net/queue1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.queue.core.usgovcloudapi.net/queue1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected the account name to be `account1` but got %q", actual.AccountName)
+ }
+
+ if actual.QueueName != "queue1" {
+ t.Fatalf("Expected the queue name to be `queue1` but got %q", actual.QueueName)
+ }
+ }
+}
diff --git a/storage/2017-07-29/queue/queues/version.go b/storage/2017-07-29/queue/queues/version.go
new file mode 100644
index 0000000..0142ce3
--- /dev/null
+++ b/storage/2017-07-29/queue/queues/version.go
@@ -0,0 +1,14 @@
+package queues
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2017-07-29"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2017-07-29/table/entities/README.md b/storage/2017-07-29/table/entities/README.md
new file mode 100644
index 0000000..e29382d
--- /dev/null
+++ b/storage/2017-07-29/table/entities/README.md
@@ -0,0 +1,48 @@
+## Table Storage Entities SDK for API version 2017-07-29
+
+This package allows you to interact with the Entities Table Storage API
+
+### Supported Authorizers
+
+* SharedKeyLite (Table)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/table/entities"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ tableName := "mytable"
+
+ storageAuth := autorest.NewSharedKeyLiteTableAuthorizer(accountName, storageAccountKey)
+ entitiesClient := entities.New()
+ entitiesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ input := entities.InsertEntityInput{
+ PartitionKey: "abc",
+ RowKey: "123",
+ MetaDataLevel: entities.NoMetaData,
+ Entity: map[string]interface{}{
+ "title": "Don't Kill My Vibe",
+ "artist": "Sigrid",
+ },
+ }
+ if _, err := entitiesClient.Insert(ctx, accountName, tableName, input); err != nil {
+ return fmt.Errorf("Error creating Entity: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2017-07-29/table/entities/client.go b/storage/2017-07-29/table/entities/client.go
new file mode 100644
index 0000000..17e9d75
--- /dev/null
+++ b/storage/2017-07-29/table/entities/client.go
@@ -0,0 +1,25 @@
+package entities
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for Table Storage Shares.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2017-07-29/table/entities/delete.go b/storage/2017-07-29/table/entities/delete.go
new file mode 100644
index 0000000..83e9188
--- /dev/null
+++ b/storage/2017-07-29/table/entities/delete.go
@@ -0,0 +1,99 @@
+package entities
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type DeleteEntityInput struct {
+ // When inserting an entity into a table, you must specify values for the PartitionKey and RowKey system properties.
+ // Together, these properties form the primary key and must be unique within the table.
+ // Both the PartitionKey and RowKey values must be string values; each key value may be up to 64 KB in size.
+ // If you are using an integer value for the key value, you should convert the integer to a fixed-width string,
+ // because they are canonically sorted. For example, you should convert the value 1 to 0000001 to ensure proper sorting.
+ RowKey string
+ PartitionKey string
+}
+
+// Delete deletes an existing entity in a table.
+func (client Client) Delete(ctx context.Context, accountName, tableName string, input DeleteEntityInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("entities.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("entities.Client", "Delete", "`tableName` cannot be an empty string.")
+ }
+ if input.PartitionKey == "" {
+ return result, validation.NewError("entities.Client", "Delete", "`input.PartitionKey` cannot be an empty string.")
+ }
+ if input.RowKey == "" {
+ return result, validation.NewError("entities.Client", "Delete", "`input.RowKey` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, tableName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "entities.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, tableName string, input DeleteEntityInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ "partitionKey": autorest.Encode("path", input.PartitionKey),
+ "rowKey": autorest.Encode("path", input.RowKey),
+ }
+
+ headers := map[string]interface{}{
+ // TODO: support for eTags
+ "If-Match": "*",
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}(PartitionKey='{partitionKey}', RowKey='{rowKey}')", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/table/entities/get.go b/storage/2017-07-29/table/entities/get.go
new file mode 100644
index 0000000..bdb4018
--- /dev/null
+++ b/storage/2017-07-29/table/entities/get.go
@@ -0,0 +1,108 @@
+package entities
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetEntityInput struct {
+ PartitionKey string
+ RowKey string
+
+ // The Level of MetaData which should be returned
+ MetaDataLevel MetaDataLevel
+}
+
+type GetEntityResult struct {
+ autorest.Response
+
+ Entity map[string]interface{}
+}
+
+// Get queries entities in a table and includes the $filter and $select options.
+func (client Client) Get(ctx context.Context, accountName, tableName string, input GetEntityInput) (result GetEntityResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("entities.Client", "Get", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("entities.Client", "Get", "`tableName` cannot be an empty string.")
+ }
+ if input.PartitionKey == "" {
+ return result, validation.NewError("entities.Client", "Get", "`input.PartitionKey` cannot be an empty string.")
+ }
+ if input.RowKey == "" {
+ return result, validation.NewError("entities.Client", "Get", "`input.RowKey` cannot be an empty string.")
+ }
+
+ req, err := client.GetPreparer(ctx, accountName, tableName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Get", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "entities.Client", "Get", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Get", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPreparer prepares the Get request.
+func (client Client) GetPreparer(ctx context.Context, accountName, tableName string, input GetEntityInput) (*http.Request, error) {
+
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ "partitionKey": autorest.Encode("path", input.PartitionKey),
+ "rowKey": autorest.Encode("path", input.RowKey),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "Accept": fmt.Sprintf("application/json;odata=%s", input.MetaDataLevel),
+ "DataServiceVersion": "3.0;NetFx",
+ "MaxDataServiceVersion": "3.0;NetFx",
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}(PartitionKey='{partitionKey}',RowKey='{rowKey}')", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetSender sends the Get request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetResponder handles the response to the Get request. The method always
+// closes the http.Response Body.
+func (client Client) GetResponder(resp *http.Response) (result GetEntityResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingJSON(&result.Entity),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/table/entities/insert.go b/storage/2017-07-29/table/entities/insert.go
new file mode 100644
index 0000000..92b05ce
--- /dev/null
+++ b/storage/2017-07-29/table/entities/insert.go
@@ -0,0 +1,112 @@
+package entities
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type InsertEntityInput struct {
+ // The level of MetaData provided for this Entity
+ MetaDataLevel MetaDataLevel
+
+ // The Entity which should be inserted, by default all values are strings
+ // To explicitly type a property, specify the appropriate OData data type by setting
+ // the m:type attribute within the property definition
+ Entity map[string]interface{}
+
+ // When inserting an entity into a table, you must specify values for the PartitionKey and RowKey system properties.
+ // Together, these properties form the primary key and must be unique within the table.
+ // Both the PartitionKey and RowKey values must be string values; each key value may be up to 64 KB in size.
+ // If you are using an integer value for the key value, you should convert the integer to a fixed-width string,
+ // because they are canonically sorted. For example, you should convert the value 1 to 0000001 to ensure proper sorting.
+ RowKey string
+ PartitionKey string
+}
+
+// Insert inserts a new entity into a table.
+func (client Client) Insert(ctx context.Context, accountName, tableName string, input InsertEntityInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("entities.Client", "Insert", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("entities.Client", "Insert", "`tableName` cannot be an empty string.")
+ }
+ if input.PartitionKey == "" {
+ return result, validation.NewError("entities.Client", "Insert", "`input.PartitionKey` cannot be an empty string.")
+ }
+ if input.RowKey == "" {
+ return result, validation.NewError("entities.Client", "Insert", "`input.RowKey` cannot be an empty string.")
+ }
+
+ req, err := client.InsertPreparer(ctx, accountName, tableName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Insert", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.InsertSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "entities.Client", "Insert", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.InsertResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Insert", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// InsertPreparer prepares the Insert request.
+func (client Client) InsertPreparer(ctx context.Context, accountName, tableName string, input InsertEntityInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "Accept": fmt.Sprintf("application/json;odata=%s", input.MetaDataLevel),
+ "Prefer": "return-no-content",
+ }
+
+ input.Entity["PartitionKey"] = input.PartitionKey
+ input.Entity["RowKey"] = input.RowKey
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/json"),
+ autorest.AsPost(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}", pathParameters),
+ autorest.WithJSON(input.Entity),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// InsertSender sends the Insert request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) InsertSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// InsertResponder handles the response to the Insert request. The method always
+// closes the http.Response Body.
+func (client Client) InsertResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/table/entities/insert_or_merge.go b/storage/2017-07-29/table/entities/insert_or_merge.go
new file mode 100644
index 0000000..1fb4ed3
--- /dev/null
+++ b/storage/2017-07-29/table/entities/insert_or_merge.go
@@ -0,0 +1,108 @@
+package entities
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type InsertOrMergeEntityInput struct {
+ // The Entity which should be inserted, by default all values are strings
+ // To explicitly type a property, specify the appropriate OData data type by setting
+ // the m:type attribute within the property definition
+ Entity map[string]interface{}
+
+ // When inserting an entity into a table, you must specify values for the PartitionKey and RowKey system properties.
+ // Together, these properties form the primary key and must be unique within the table.
+ // Both the PartitionKey and RowKey values must be string values; each key value may be up to 64 KB in size.
+ // If you are using an integer value for the key value, you should convert the integer to a fixed-width string,
+ // because they are canonically sorted. For example, you should convert the value 1 to 0000001 to ensure proper sorting.
+ RowKey string
+ PartitionKey string
+}
+
+// InsertOrMerge updates an existing entity or inserts a new entity if it does not exist in the table.
+// Because this operation can insert or update an entity, it is also known as an upsert operation.
+func (client Client) InsertOrMerge(ctx context.Context, accountName, tableName string, input InsertOrMergeEntityInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("entities.Client", "InsertOrMerge", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("entities.Client", "InsertOrMerge", "`tableName` cannot be an empty string.")
+ }
+ if input.PartitionKey == "" {
+ return result, validation.NewError("entities.Client", "InsertOrMerge", "`input.PartitionKey` cannot be an empty string.")
+ }
+ if input.RowKey == "" {
+ return result, validation.NewError("entities.Client", "InsertOrMerge", "`input.RowKey` cannot be an empty string.")
+ }
+
+ req, err := client.InsertOrMergePreparer(ctx, accountName, tableName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "InsertOrMerge", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.InsertOrMergeSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "entities.Client", "InsertOrMerge", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.InsertOrMergeResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "InsertOrMerge", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// InsertOrMergePreparer prepares the InsertOrMerge request.
+func (client Client) InsertOrMergePreparer(ctx context.Context, accountName, tableName string, input InsertOrMergeEntityInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ "partitionKey": autorest.Encode("path", input.PartitionKey),
+ "rowKey": autorest.Encode("path", input.RowKey),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "Accept": "application/json",
+ "Prefer": "return-no-content",
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/json"),
+ autorest.AsMerge(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}(PartitionKey='{partitionKey}', RowKey='{rowKey}')", pathParameters),
+ autorest.WithJSON(input.Entity),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// InsertOrMergeSender sends the InsertOrMerge request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) InsertOrMergeSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// InsertOrMergeResponder handles the response to the InsertOrMerge request. The method always
+// closes the http.Response Body.
+func (client Client) InsertOrMergeResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/table/entities/insert_or_replace.go b/storage/2017-07-29/table/entities/insert_or_replace.go
new file mode 100644
index 0000000..036ba5d
--- /dev/null
+++ b/storage/2017-07-29/table/entities/insert_or_replace.go
@@ -0,0 +1,108 @@
+package entities
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type InsertOrReplaceEntityInput struct {
+ // The Entity which should be inserted, by default all values are strings
+ // To explicitly type a property, specify the appropriate OData data type by setting
+ // the m:type attribute within the property definition
+ Entity map[string]interface{}
+
+ // When inserting an entity into a table, you must specify values for the PartitionKey and RowKey system properties.
+ // Together, these properties form the primary key and must be unique within the table.
+ // Both the PartitionKey and RowKey values must be string values; each key value may be up to 64 KB in size.
+ // If you are using an integer value for the key value, you should convert the integer to a fixed-width string,
+ // because they are canonically sorted. For example, you should convert the value 1 to 0000001 to ensure proper sorting.
+ RowKey string
+ PartitionKey string
+}
+
+// InsertOrReplace replaces an existing entity or inserts a new entity if it does not exist in the table.
+// Because this operation can insert or update an entity, it is also known as an upsert operation.
+func (client Client) InsertOrReplace(ctx context.Context, accountName, tableName string, input InsertOrReplaceEntityInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("entities.Client", "InsertOrReplace", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("entities.Client", "InsertOrReplace", "`tableName` cannot be an empty string.")
+ }
+ if input.PartitionKey == "" {
+ return result, validation.NewError("entities.Client", "InsertOrReplace", "`input.PartitionKey` cannot be an empty string.")
+ }
+ if input.RowKey == "" {
+ return result, validation.NewError("entities.Client", "InsertOrReplace", "`input.RowKey` cannot be an empty string.")
+ }
+
+ req, err := client.InsertOrReplacePreparer(ctx, accountName, tableName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "InsertOrReplace", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.InsertOrReplaceSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "entities.Client", "InsertOrReplace", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.InsertOrReplaceResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "InsertOrReplace", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// InsertOrReplacePreparer prepares the InsertOrReplace request.
+func (client Client) InsertOrReplacePreparer(ctx context.Context, accountName, tableName string, input InsertOrReplaceEntityInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ "partitionKey": autorest.Encode("path", input.PartitionKey),
+ "rowKey": autorest.Encode("path", input.RowKey),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "Accept": "application/json",
+ "Prefer": "return-no-content",
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/json"),
+ autorest.AsMerge(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}(PartitionKey='{partitionKey}', RowKey='{rowKey}')", pathParameters),
+ autorest.WithJSON(input.Entity),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// InsertOrReplaceSender sends the InsertOrReplace request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) InsertOrReplaceSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// InsertOrReplaceResponder handles the response to the InsertOrReplace request. The method always
+// closes the http.Response Body.
+func (client Client) InsertOrReplaceResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/table/entities/lifecycle_test.go b/storage/2017-07-29/table/entities/lifecycle_test.go
new file mode 100644
index 0000000..8e05049
--- /dev/null
+++ b/storage/2017-07-29/table/entities/lifecycle_test.go
@@ -0,0 +1,135 @@
+package entities
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/table/tables"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestEntitiesLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ tableName := fmt.Sprintf("table%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteTableAuthorizer(accountName, testData.StorageAccountKey)
+ tablesClient := tables.NewWithEnvironment(client.Environment)
+ tablesClient.Client = client.PrepareWithAuthorizer(tablesClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Creating Table..")
+ if _, err := tablesClient.Create(ctx, accountName, tableName); err != nil {
+ t.Fatalf("Error creating Table %q: %s", tableName, err)
+ }
+ defer tablesClient.Delete(ctx, accountName, tableName)
+
+ entitiesClient := NewWithEnvironment(client.Environment)
+ entitiesClient.Client = client.PrepareWithAuthorizer(entitiesClient.Client, storageAuth)
+
+ partitionKey := "hello"
+ rowKey := "there"
+
+ t.Logf("[DEBUG] Inserting..")
+ insertInput := InsertEntityInput{
+ MetaDataLevel: NoMetaData,
+ PartitionKey: partitionKey,
+ RowKey: rowKey,
+ Entity: map[string]interface{}{
+ "hello": "world",
+ },
+ }
+ if _, err := entitiesClient.Insert(ctx, accountName, tableName, insertInput); err != nil {
+ t.Logf("Error retrieving: %s", err)
+ }
+
+ t.Logf("[DEBUG] Insert or Merging..")
+ insertOrMergeInput := InsertOrMergeEntityInput{
+ PartitionKey: partitionKey,
+ RowKey: rowKey,
+ Entity: map[string]interface{}{
+ "hello": "ther88e",
+ },
+ }
+ if _, err := entitiesClient.InsertOrMerge(ctx, accountName, tableName, insertOrMergeInput); err != nil {
+ t.Logf("Error insert/merging: %s", err)
+ }
+
+ t.Logf("[DEBUG] Insert or Replacing..")
+ insertOrReplaceInput := InsertOrReplaceEntityInput{
+ PartitionKey: partitionKey,
+ RowKey: rowKey,
+ Entity: map[string]interface{}{
+ "hello": "pandas",
+ },
+ }
+ if _, err := entitiesClient.InsertOrReplace(ctx, accountName, tableName, insertOrReplaceInput); err != nil {
+ t.Logf("Error inserting/replacing: %s", err)
+ }
+
+ t.Logf("[DEBUG] Querying..")
+ queryInput := QueryEntitiesInput{
+ MetaDataLevel: NoMetaData,
+ }
+ results, err := entitiesClient.Query(ctx, accountName, tableName, queryInput)
+ if err != nil {
+ t.Logf("Error querying: %s", err)
+ }
+
+ if len(results.Entities) != 1 {
+ t.Fatalf("Expected 1 item but got %d", len(results.Entities))
+ }
+
+ for _, v := range results.Entities {
+ thisPartitionKey := v["PartitionKey"].(string)
+ thisRowKey := v["RowKey"].(string)
+ if partitionKey != thisPartitionKey {
+ t.Fatalf("Expected Partition Key to be %q but got %q", partitionKey, thisPartitionKey)
+ }
+ if rowKey != thisRowKey {
+ t.Fatalf("Expected Partition Key to be %q but got %q", rowKey, thisRowKey)
+ }
+ }
+
+ t.Logf("[DEBUG] Retrieving..")
+ getInput := GetEntityInput{
+ MetaDataLevel: MinimalMetaData,
+ PartitionKey: partitionKey,
+ RowKey: rowKey,
+ }
+ getResults, err := entitiesClient.Get(ctx, accountName, tableName, getInput)
+ if err != nil {
+ t.Logf("Error querying: %s", err)
+ }
+
+ partitionKey2 := getResults.Entity["PartitionKey"].(string)
+ rowKey2 := getResults.Entity["RowKey"].(string)
+ if partitionKey2 != partitionKey {
+ t.Fatalf("Expected Partition Key to be %q but got %q", partitionKey, partitionKey2)
+ }
+ if rowKey2 != rowKey {
+ t.Fatalf("Expected Row Key to be %q but got %q", rowKey, rowKey2)
+ }
+
+ t.Logf("[DEBUG] Deleting..")
+ deleteInput := DeleteEntityInput{
+ PartitionKey: partitionKey,
+ RowKey: rowKey,
+ }
+ if _, err := entitiesClient.Delete(ctx, accountName, tableName, deleteInput); err != nil {
+ t.Logf("Error deleting: %s", err)
+ }
+}
diff --git a/storage/2017-07-29/table/entities/models.go b/storage/2017-07-29/table/entities/models.go
new file mode 100644
index 0000000..e3c6ccc
--- /dev/null
+++ b/storage/2017-07-29/table/entities/models.go
@@ -0,0 +1,9 @@
+package entities
+
+type MetaDataLevel string
+
+var (
+ NoMetaData MetaDataLevel = "nometadata"
+ MinimalMetaData MetaDataLevel = "minimalmetadata"
+ FullMetaData MetaDataLevel = "fullmetadata"
+)
diff --git a/storage/2017-07-29/table/entities/query.go b/storage/2017-07-29/table/entities/query.go
new file mode 100644
index 0000000..a768b83
--- /dev/null
+++ b/storage/2017-07-29/table/entities/query.go
@@ -0,0 +1,155 @@
+package entities
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type QueryEntitiesInput struct {
+ // An optional OData filter
+ Filter *string
+
+ // An optional comma-separated
+ PropertyNamesToSelect *[]string
+
+ PartitionKey string
+ RowKey string
+
+ // The Level of MetaData which should be returned
+ MetaDataLevel MetaDataLevel
+
+ // The Next Partition Key used to load data from a previous point
+ NextPartitionKey *string
+
+ // The Next Row Key used to load data from a previous point
+ NextRowKey *string
+}
+
+type QueryEntitiesResult struct {
+ autorest.Response
+
+ NextPartitionKey string
+ NextRowKey string
+
+ MetaData string `json:"odata.metadata,omitempty"`
+ Entities []map[string]interface{} `json:"value"`
+}
+
+// Query queries entities in a table and includes the $filter and $select options.
+func (client Client) Query(ctx context.Context, accountName, tableName string, input QueryEntitiesInput) (result QueryEntitiesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("entities.Client", "Query", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("entities.Client", "Query", "`tableName` cannot be an empty string.")
+ }
+
+ req, err := client.QueryPreparer(ctx, accountName, tableName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Query", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.QuerySender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "entities.Client", "Query", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.QueryResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Query", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// QueryPreparer prepares the Query request.
+func (client Client) QueryPreparer(ctx context.Context, accountName, tableName string, input QueryEntitiesInput) (*http.Request, error) {
+
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ "additionalParameters": "",
+ }
+
+ //PartitionKey='',RowKey=''
+ additionalParams := make([]string, 0)
+ if input.PartitionKey != "" {
+ additionalParams = append(additionalParams, fmt.Sprintf("PartitionKey='%s'", input.PartitionKey))
+ }
+ if input.RowKey != "" {
+ additionalParams = append(additionalParams, fmt.Sprintf("RowKey='%s'", input.RowKey))
+ }
+ if len(additionalParams) > 0 {
+ pathParameters["additionalParameters"] = autorest.Encode("path", strings.Join(additionalParams, ","))
+ }
+
+ queryParameters := map[string]interface{}{}
+
+ if input.Filter != nil {
+ queryParameters["filter"] = autorest.Encode("query", input.Filter)
+ }
+
+ if input.PropertyNamesToSelect != nil {
+ queryParameters["$select"] = autorest.Encode("query", strings.Join(*input.PropertyNamesToSelect, ","))
+ }
+
+ if input.NextPartitionKey != nil {
+ queryParameters["NextPartitionKey"] = *input.NextPartitionKey
+ }
+
+ if input.NextRowKey != nil {
+ queryParameters["NextRowKey"] = *input.NextRowKey
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "Accept": fmt.Sprintf("application/json;odata=%s", input.MetaDataLevel),
+ "DataServiceVersion": "3.0;NetFx",
+ "MaxDataServiceVersion": "3.0;NetFx",
+ }
+
+ // GET /myaccount/Customers()?$filter=(Rating%20ge%203)%20and%20(Rating%20le%206)&$select=PartitionKey,RowKey,Address,CustomerSince
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}({additionalParameters})", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// QuerySender sends the Query request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) QuerySender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// QueryResponder handles the response to the Query request. The method always
+// closes the http.Response Body.
+func (client Client) QueryResponder(resp *http.Response) (result QueryEntitiesResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.NextPartitionKey = resp.Header.Get("x-ms-continuation-NextPartitionKey")
+ result.NextRowKey = resp.Header.Get("x-ms-continuation-NextRowKey")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingJSON(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/table/entities/resource_id.go b/storage/2017-07-29/table/entities/resource_id.go
new file mode 100644
index 0000000..59366a2
--- /dev/null
+++ b/storage/2017-07-29/table/entities/resource_id.go
@@ -0,0 +1,91 @@
+package entities
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Entity
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, tableName, partitionKey, rowKey string) string {
+ domain := endpoints.GetTableEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s(PartitionKey='%s',RowKey='%s')", domain, tableName, partitionKey, rowKey)
+}
+
+type ResourceID struct {
+ AccountName string
+ TableName string
+ PartitionKey string
+ RowKey string
+}
+
+// ParseResourceID parses the specified Resource ID and returns an object which
+// can be used to look up the specified Entity within the specified Table
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://account1.table.core.chinacloudapi.cn/table1(PartitionKey='partition1',RowKey='row1')
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ // assume there a `Table('')`
+ path := strings.TrimPrefix(uri.Path, "/")
+ if !strings.Contains(uri.Path, "(") || !strings.HasSuffix(uri.Path, ")") {
+ return nil, fmt.Errorf("Expected the Table Name to be in the format `tables(PartitionKey='',RowKey='')` but got %q", path)
+ }
+
+ // NOTE: honestly this could probably be a RegEx, but this seemed like the simplest way to
+ // allow these two fields to be specified in either order
+ indexOfBracket := strings.IndexByte(path, '(')
+ tableName := path[0:indexOfBracket]
+
+ // trim off the brackets
+ temp := strings.TrimPrefix(path, fmt.Sprintf("%s(", tableName))
+ temp = strings.TrimSuffix(temp, ")")
+
+ dictionary := strings.Split(temp, ",")
+ partitionKey := ""
+ rowKey := ""
+ for _, v := range dictionary {
+ split := strings.Split(v, "=")
+ if len(split) != 2 {
+ return nil, fmt.Errorf("Expected 2 segments but got %d for %q", len(split), v)
+ }
+
+ key := split[0]
+ value := strings.TrimSuffix(strings.TrimPrefix(split[1], "'"), "'")
+ if strings.EqualFold(key, "PartitionKey") {
+ partitionKey = value
+ } else if strings.EqualFold(key, "RowKey") {
+ rowKey = value
+ } else {
+ return nil, fmt.Errorf("Unexpected Key %q", key)
+ }
+ }
+
+ if partitionKey == "" {
+ return nil, fmt.Errorf("Expected a PartitionKey but didn't get one")
+ }
+ if rowKey == "" {
+ return nil, fmt.Errorf("Expected a RowKey but didn't get one")
+ }
+
+ return &ResourceID{
+ AccountName: *accountName,
+ TableName: tableName,
+ PartitionKey: partitionKey,
+ RowKey: rowKey,
+ }, nil
+}
diff --git a/storage/2017-07-29/table/entities/resource_id_test.go b/storage/2017-07-29/table/entities/resource_id_test.go
new file mode 100644
index 0000000..e85af79
--- /dev/null
+++ b/storage/2017-07-29/table/entities/resource_id_test.go
@@ -0,0 +1,84 @@
+package entities
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.table.core.chinacloudapi.cn/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.table.core.cloudapi.de/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.table.core.windows.net/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.table.core.usgovcloudapi.net/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "table1", "partition1", "row1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.table.core.chinacloudapi.cn/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.table.core.cloudapi.de/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.table.core.windows.net/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.table.core.usgovcloudapi.net/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.TableName != "table1" {
+ t.Fatalf("Expected Table Name to be `table1` but got %q", actual.TableName)
+ }
+ if actual.PartitionKey != "partition1" {
+ t.Fatalf("Expected Partition Key to be `partition1` but got %q", actual.PartitionKey)
+ }
+ if actual.RowKey != "row1" {
+ t.Fatalf("Expected Row Key to be `row1` but got %q", actual.RowKey)
+ }
+ }
+}
diff --git a/storage/2017-07-29/table/entities/version.go b/storage/2017-07-29/table/entities/version.go
new file mode 100644
index 0000000..3b8c657
--- /dev/null
+++ b/storage/2017-07-29/table/entities/version.go
@@ -0,0 +1,14 @@
+package entities
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2017-07-29"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2017-07-29/table/tables/README.md b/storage/2017-07-29/table/tables/README.md
new file mode 100644
index 0000000..38f14ed
--- /dev/null
+++ b/storage/2017-07-29/table/tables/README.md
@@ -0,0 +1,39 @@
+## Table Storage Tables SDK for API version 2017-07-29
+
+This package allows you to interact with the Tables Table Storage API
+
+### Supported Authorizers
+
+* SharedKeyLite (Table)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2017-07-29/table/tables"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ tableName := "mytable"
+
+ storageAuth := autorest.NewSharedKeyLiteTableAuthorizer(accountName, storageAccountKey)
+ tablesClient := tables.New()
+ tablesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ if _, err := tablesClient.Insert(ctx, accountName, tableName); err != nil {
+ return fmt.Errorf("Error creating Table: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2017-07-29/table/tables/acl_get.go b/storage/2017-07-29/table/tables/acl_get.go
new file mode 100644
index 0000000..0ef0000
--- /dev/null
+++ b/storage/2017-07-29/table/tables/acl_get.go
@@ -0,0 +1,93 @@
+package tables
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetACLResult struct {
+ autorest.Response
+
+ SignedIdentifiers []SignedIdentifier `xml:"SignedIdentifier"`
+}
+
+// GetACL returns the Access Control List for the specified Table
+func (client Client) GetACL(ctx context.Context, accountName, tableName string) (result GetACLResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("tables.Client", "GetACL", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("tables.Client", "GetACL", "`tableName` cannot be an empty string.")
+ }
+
+ req, err := client.GetACLPreparer(ctx, accountName, tableName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "GetACL", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetACLSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "tables.Client", "GetACL", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetACLResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "GetACL", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetACLPreparer prepares the GetACL request.
+func (client Client) GetACLPreparer(ctx context.Context, accountName, tableName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "acl"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetACLSender sends the GetACL request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetACLSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetACLResponder handles the response to the GetACL request. The method always
+// closes the http.Response Body.
+func (client Client) GetACLResponder(resp *http.Response) (result GetACLResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/table/tables/acl_set.go b/storage/2017-07-29/table/tables/acl_set.go
new file mode 100644
index 0000000..c26bffc
--- /dev/null
+++ b/storage/2017-07-29/table/tables/acl_set.go
@@ -0,0 +1,98 @@
+package tables
+
+import (
+ "context"
+ "encoding/xml"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type setAcl struct {
+ SignedIdentifiers []SignedIdentifier `xml:"SignedIdentifier"`
+
+ XMLName xml.Name `xml:"SignedIdentifiers"`
+}
+
+// SetACL sets the specified Access Control List for the specified Table
+func (client Client) SetACL(ctx context.Context, accountName, tableName string, acls []SignedIdentifier) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("tables.Client", "SetACL", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("tables.Client", "SetACL", "`tableName` cannot be an empty string.")
+ }
+
+ req, err := client.SetACLPreparer(ctx, accountName, tableName, acls)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "SetACL", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetACLSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "tables.Client", "SetACL", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetACLResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "SetACL", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetACLPreparer prepares the SetACL request.
+func (client Client) SetACLPreparer(ctx context.Context, accountName, tableName string, acls []SignedIdentifier) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "acl"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ input := setAcl{
+ SignedIdentifiers: acls,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithXML(&input))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetACLSender sends the SetACL request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetACLSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetACLResponder handles the response to the SetACL request. The method always
+// closes the http.Response Body.
+func (client Client) SetACLResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/table/tables/client.go b/storage/2017-07-29/table/tables/client.go
new file mode 100644
index 0000000..56724b9
--- /dev/null
+++ b/storage/2017-07-29/table/tables/client.go
@@ -0,0 +1,25 @@
+package tables
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for Table Storage Shares.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2017-07-29/table/tables/create.go b/storage/2017-07-29/table/tables/create.go
new file mode 100644
index 0000000..561f574
--- /dev/null
+++ b/storage/2017-07-29/table/tables/create.go
@@ -0,0 +1,90 @@
+package tables
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type createTableRequest struct {
+ TableName string `json:"TableName"`
+}
+
+// Create creates a new table in the storage account.
+func (client Client) Create(ctx context.Context, accountName, tableName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("tables.Client", "Create", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("tables.Client", "Create", "`tableName` cannot be an empty string.")
+ }
+
+ req, err := client.CreatePreparer(ctx, accountName, tableName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Create", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "tables.Client", "Create", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Create", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreatePreparer prepares the Create request.
+func (client Client) CreatePreparer(ctx context.Context, accountName, tableName string) (*http.Request, error) {
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ // NOTE: we could support returning metadata here, but it doesn't appear to be directly useful
+ // vs making a request using the Get methods as-necessary?
+ "Accept": "application/json;odata=nometadata",
+ "Prefer": "return-no-content",
+ }
+
+ body := createTableRequest{
+ TableName: tableName,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/json"),
+ autorest.AsPost(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPath("/Tables"),
+ autorest.WithJSON(body),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSender sends the Create request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateResponder handles the response to the Create request. The method always
+// closes the http.Response Body.
+func (client Client) CreateResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/table/tables/delete.go b/storage/2017-07-29/table/tables/delete.go
new file mode 100644
index 0000000..5b5ec86
--- /dev/null
+++ b/storage/2017-07-29/table/tables/delete.go
@@ -0,0 +1,79 @@
+package tables
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete deletes the specified table and any data it contains.
+func (client Client) Delete(ctx context.Context, accountName, tableName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("tables.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("tables.Client", "Delete", "`tableName` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, tableName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "tables.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, tableName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ }
+
+ // NOTE: whilst the API documentation says that API Version is Optional
+ // apparently specifying it causes an "invalid content type" to always be returned
+ // as such we omit it here :shrug:
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/Tables('{tableName}')", pathParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/table/tables/exists.go b/storage/2017-07-29/table/tables/exists.go
new file mode 100644
index 0000000..b3a2718
--- /dev/null
+++ b/storage/2017-07-29/table/tables/exists.go
@@ -0,0 +1,80 @@
+package tables
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Exists checks that the specified table exists
+func (client Client) Exists(ctx context.Context, accountName, tableName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("tables.Client", "Exists", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("tables.Client", "Exists", "`tableName` cannot be an empty string.")
+ }
+
+ req, err := client.ExistsPreparer(ctx, accountName, tableName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Exists", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ExistsSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "tables.Client", "Exists", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ExistsResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Exists", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ExistsPreparer prepares the Exists request.
+func (client Client) ExistsPreparer(ctx context.Context, accountName, tableName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ }
+
+ // NOTE: whilst the API documentation says that API Version is Optional
+ // apparently specifying it causes an "invalid content type" to always be returned
+ // as such we omit it here :shrug:
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.AsContentType("application/xml"),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/Tables('{tableName}')", pathParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ExistsSender sends the Exists request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ExistsSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ExistsResponder handles the response to the Exists request. The method always
+// closes the http.Response Body.
+func (client Client) ExistsResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/table/tables/lifecycle_test.go b/storage/2017-07-29/table/tables/lifecycle_test.go
new file mode 100644
index 0000000..74ab0fe
--- /dev/null
+++ b/storage/2017-07-29/table/tables/lifecycle_test.go
@@ -0,0 +1,112 @@
+package tables
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestTablesLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ tableName := fmt.Sprintf("table%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteTableAuthorizer(accountName, testData.StorageAccountKey)
+ tablesClient := NewWithEnvironment(client.Environment)
+ tablesClient.Client = client.PrepareWithAuthorizer(tablesClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Creating Table..")
+ if _, err := tablesClient.Create(ctx, accountName, tableName); err != nil {
+ t.Fatalf("Error creating Table %q: %s", tableName, err)
+ }
+
+ // first look it up directly and confirm it's there
+ t.Logf("[DEBUG] Checking if Table exists..")
+ if _, err := tablesClient.Exists(ctx, accountName, tableName); err != nil {
+ t.Fatalf("Error checking if Table %q exists: %s", tableName, err)
+ }
+
+ // then confirm it exists in the Query too
+ t.Logf("[DEBUG] Querying for Tables..")
+ result, err := tablesClient.Query(ctx, accountName, NoMetaData)
+ if err != nil {
+ t.Fatalf("Error retrieving Tables: %s", err)
+ }
+ found := false
+ for _, v := range result.Tables {
+ log.Printf("[DEBUG] Table: %q", v.TableName)
+
+ if v.TableName == tableName {
+ found = true
+ }
+ }
+ if !found {
+ t.Fatalf("%q was not found in the Query response!", tableName)
+ }
+
+ t.Logf("[DEBUG] Setting ACL's for Table %q..", tableName)
+ acls := []SignedIdentifier{
+ {
+ Id: "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=",
+ AccessPolicy: AccessPolicy{
+ Permission: "raud",
+ Start: "2020-11-26T08:49:37.0000000Z",
+ Expiry: "2020-11-27T08:49:37.0000000Z",
+ },
+ },
+ }
+ if _, err := tablesClient.SetACL(ctx, accountName, tableName, acls); err != nil {
+ t.Fatalf("Error setting ACLs: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving ACL's for Table %q..", tableName)
+ retrievedACLs, err := tablesClient.GetACL(ctx, accountName, tableName)
+ if err != nil {
+ t.Fatalf("Error retrieving ACLs: %s", err)
+ }
+
+ if len(retrievedACLs.SignedIdentifiers) != len(acls) {
+ t.Fatalf("Expected %d but got %q ACLs", len(retrievedACLs.SignedIdentifiers), len(acls))
+ }
+
+ for i, retrievedAcl := range retrievedACLs.SignedIdentifiers {
+ expectedAcl := acls[i]
+
+ if retrievedAcl.Id != expectedAcl.Id {
+ t.Fatalf("Expected ID to be %q but got %q", retrievedAcl.Id, expectedAcl.Id)
+ }
+
+ if retrievedAcl.AccessPolicy.Start != expectedAcl.AccessPolicy.Start {
+ t.Fatalf("Expected Start to be %q but got %q", retrievedAcl.AccessPolicy.Start, expectedAcl.AccessPolicy.Start)
+ }
+
+ if retrievedAcl.AccessPolicy.Expiry != expectedAcl.AccessPolicy.Expiry {
+ t.Fatalf("Expected Expiry to be %q but got %q", retrievedAcl.AccessPolicy.Expiry, expectedAcl.AccessPolicy.Expiry)
+ }
+
+ if retrievedAcl.AccessPolicy.Permission != expectedAcl.AccessPolicy.Permission {
+ t.Fatalf("Expected Permission to be %q but got %q", retrievedAcl.AccessPolicy.Permission, expectedAcl.AccessPolicy.Permission)
+ }
+ }
+
+ t.Logf("[DEBUG] Deleting Table %q..", tableName)
+ if _, err := tablesClient.Delete(ctx, accountName, tableName); err != nil {
+ t.Fatalf("Error deleting %q: %s", tableName, err)
+ }
+}
diff --git a/storage/2017-07-29/table/tables/models.go b/storage/2017-07-29/table/tables/models.go
new file mode 100644
index 0000000..d7c382a
--- /dev/null
+++ b/storage/2017-07-29/table/tables/models.go
@@ -0,0 +1,29 @@
+package tables
+
+type MetaDataLevel string
+
+var (
+ NoMetaData MetaDataLevel = "nometadata"
+ MinimalMetaData MetaDataLevel = "minimalmetadata"
+ FullMetaData MetaDataLevel = "fullmetadata"
+)
+
+type GetResultItem struct {
+ TableName string `json:"TableName"`
+
+ // Optional, depending on the MetaData Level
+ ODataType string `json:"odata.type,omitempty"`
+ ODataID string `json:"odata.id,omitEmpty"`
+ ODataEditLink string `json:"odata.editLink,omitEmpty"`
+}
+
+type SignedIdentifier struct {
+ Id string `xml:"Id"`
+ AccessPolicy AccessPolicy `xml:"AccessPolicy"`
+}
+
+type AccessPolicy struct {
+ Start string `xml:"Start"`
+ Expiry string `xml:"Expiry"`
+ Permission string `xml:"Permission"`
+}
diff --git a/storage/2017-07-29/table/tables/query.go b/storage/2017-07-29/table/tables/query.go
new file mode 100644
index 0000000..475370f
--- /dev/null
+++ b/storage/2017-07-29/table/tables/query.go
@@ -0,0 +1,87 @@
+package tables
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetResult struct {
+ autorest.Response
+
+ MetaData string `json:"odata.metadata,omitempty"`
+ Tables []GetResultItem `json:"value"`
+}
+
+// Query returns a list of tables under the specified account.
+func (client Client) Query(ctx context.Context, accountName string, metaDataLevel MetaDataLevel) (result GetResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("tables.Client", "Query", "`accountName` cannot be an empty string.")
+ }
+
+ req, err := client.QueryPreparer(ctx, accountName, metaDataLevel)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Query", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.QuerySender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "tables.Client", "Query", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.QueryResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Query", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// QueryPreparer prepares the Query request.
+func (client Client) QueryPreparer(ctx context.Context, accountName string, metaDataLevel MetaDataLevel) (*http.Request, error) {
+ // NOTE: whilst this supports ContinuationTokens and 'Top'
+ // it appears that 'Skip' returns a '501 Not Implemented'
+ // as such, we intentionally don't support those right now
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "Accept": fmt.Sprintf("application/json;odata=%s", metaDataLevel),
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPath("/Tables"),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// QuerySender sends the Query request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) QuerySender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// QueryResponder handles the response to the Query request. The method always
+// closes the http.Response Body.
+func (client Client) QueryResponder(resp *http.Response) (result GetResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingJSON(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2017-07-29/table/tables/resource_id.go b/storage/2017-07-29/table/tables/resource_id.go
new file mode 100644
index 0000000..1052317
--- /dev/null
+++ b/storage/2017-07-29/table/tables/resource_id.go
@@ -0,0 +1,54 @@
+package tables
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Table
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, tableName string) string {
+ domain := endpoints.GetTableEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/Tables('%s')", domain, tableName)
+}
+
+type ResourceID struct {
+ AccountName string
+ TableName string
+}
+
+// ParseResourceID parses the Resource ID and returns an object which
+// can be used to interact with the Table within the specified Storage Account
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://foo.table.core.windows.net/Table('foo')
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ // assume there a `Table('')`
+ path := strings.TrimPrefix(uri.Path, "/")
+ if !strings.HasPrefix(path, "Tables('") || !strings.HasSuffix(path, "')") {
+ return nil, fmt.Errorf("Expected the Table Name to be in the format `Tables('name')` but got %q", path)
+ }
+
+ // strip off the `Table('')`
+ tableName := strings.TrimPrefix(uri.Path, "/Tables('")
+ tableName = strings.TrimSuffix(tableName, "')")
+ return &ResourceID{
+ AccountName: *accountName,
+ TableName: tableName,
+ }, nil
+}
diff --git a/storage/2017-07-29/table/tables/resource_id_test.go b/storage/2017-07-29/table/tables/resource_id_test.go
new file mode 100644
index 0000000..5557f81
--- /dev/null
+++ b/storage/2017-07-29/table/tables/resource_id_test.go
@@ -0,0 +1,78 @@
+package tables
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.table.core.chinacloudapi.cn/Tables('table1')",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.table.core.cloudapi.de/Tables('table1')",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.table.core.windows.net/Tables('table1')",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.table.core.usgovcloudapi.net/Tables('table1')",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "table1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.table.core.chinacloudapi.cn/Tables('table1')",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.table.core.cloudapi.de/Tables('table1')",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.table.core.windows.net/Tables('table1')",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.table.core.usgovcloudapi.net/Tables('table1')",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.TableName != "table1" {
+ t.Fatalf("Expected Table Name to be `table1` but got %q", actual.TableName)
+ }
+ }
+}
diff --git a/storage/2017-07-29/table/tables/version.go b/storage/2017-07-29/table/tables/version.go
new file mode 100644
index 0000000..a174db6
--- /dev/null
+++ b/storage/2017-07-29/table/tables/version.go
@@ -0,0 +1,14 @@
+package tables
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2017-07-29"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2018-03-28/README.md b/storage/2018-03-28/README.md
new file mode 100644
index 0000000..375d9d9
--- /dev/null
+++ b/storage/2018-03-28/README.md
@@ -0,0 +1,25 @@
+# Storage API Version 2018-03-28
+
+The following API's are supported by this SDK - more information about each SDK can be found within the README in each package.
+
+## Blob Storage
+
+- [Blobs API](blob/blobs)
+- [Containers API](blob/containers)
+
+## File Storage
+
+- [Directories API](file/directories)
+- [Files API](file/files)
+- [Shares API](file/shares)
+
+## Queue Storage
+
+- [Queues API](queue/queues)
+- [Messages API](queue/messages)
+
+## Table Storage
+
+- [Entities API](table/entities)
+- [Tables API](table/tables)
+
diff --git a/storage/2018-03-28/blob/blobs/README.md b/storage/2018-03-28/blob/blobs/README.md
new file mode 100644
index 0000000..930e2d7
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/README.md
@@ -0,0 +1,46 @@
+## Blob Storage Blobs SDK for API version 2018-03-28
+
+This package allows you to interact with the Blobs Blob Storage API
+
+### Supported Authorizers
+
+* Azure Active Directory (for the Resource Endpoint `https://storage.azure.com`)
+* SharedKeyLite (Blob, File & Queue)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/blob/blobs"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ containerName := "mycontainer"
+ fileName := "example-large-file.iso"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ blobClient := blobs.New()
+ blobClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ copyInput := blobs.CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+ refreshInterval := 5 * time.Second
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, fileName, copyInput, refreshInterval); err != nil {
+ return fmt.Errorf("Error copying: %s", err)
+ }
+
+ return nil
+}
+
+```
\ No newline at end of file
diff --git a/storage/2018-03-28/blob/blobs/append_block.go b/storage/2018-03-28/blob/blobs/append_block.go
new file mode 100644
index 0000000..7fed86a
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/append_block.go
@@ -0,0 +1,170 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type AppendBlockInput struct {
+
+ // A number indicating the byte offset to compare.
+ // Append Block will succeed only if the append position is equal to this number.
+ // If it is not, the request will fail with an AppendPositionConditionNotMet
+ // error (HTTP status code 412 – Precondition Failed)
+ BlobConditionAppendPosition *int64
+
+ // The max length in bytes permitted for the append blob.
+ // If the Append Block operation would cause the blob to exceed that limit or if the blob size
+ // is already greater than the value specified in this header, the request will fail with
+ // an MaxBlobSizeConditionNotMet error (HTTP status code 412 – Precondition Failed).
+ BlobConditionMaxSize *int64
+
+ // The Bytes which should be appended to the end of this Append Blob.
+ Content []byte
+
+ // An MD5 hash of the block content.
+ // This hash is used to verify the integrity of the block during transport.
+ // When this header is specified, the storage service compares the hash of the content
+ // that has arrived with this header value.
+ //
+ // Note that this MD5 hash is not stored with the blob.
+ // If the two hashes do not match, the operation will fail with error code 400 (Bad Request).
+ ContentMD5 *string
+
+ // Required if the blob has an active lease.
+ // To perform this operation on a blob with an active lease, specify the valid lease ID for this header.
+ LeaseID *string
+}
+
+type AppendBlockResult struct {
+ autorest.Response
+
+ BlobAppendOffset string
+ BlobCommittedBlockCount int64
+ ContentMD5 string
+ ETag string
+ LastModified string
+}
+
+// AppendBlock commits a new block of data to the end of an existing append blob.
+func (client Client) AppendBlock(ctx context.Context, accountName, containerName, blobName string, input AppendBlockInput) (result AppendBlockResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "AppendBlock", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "AppendBlock", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "AppendBlock", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "AppendBlock", "`blobName` cannot be an empty string.")
+ }
+ if len(input.Content) > (4 * 1024 * 1024) {
+ return result, validation.NewError("files.Client", "PutByteRange", "`input.Content` must be at most 4MB.")
+ }
+
+ req, err := client.AppendBlockPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AppendBlock", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.AppendBlockSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AppendBlock", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.AppendBlockResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AppendBlock", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// AppendBlockPreparer prepares the AppendBlock request.
+func (client Client) AppendBlockPreparer(ctx context.Context, accountName, containerName, blobName string, input AppendBlockInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "appendblock"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.BlobConditionAppendPosition != nil {
+ headers["x-ms-blob-condition-appendpos"] = *input.BlobConditionAppendPosition
+ }
+ if input.BlobConditionMaxSize != nil {
+ headers["x-ms-blob-condition-maxsize"] = *input.BlobConditionMaxSize
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithBytes(&input.Content))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// AppendBlockSender sends the AppendBlock request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) AppendBlockSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// AppendBlockResponder handles the response to the AppendBlock request. The method always
+// closes the http.Response Body.
+func (client Client) AppendBlockResponder(resp *http.Response) (result AppendBlockResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.BlobAppendOffset = resp.Header.Get("x-ms-blob-append-offset")
+ result.ContentMD5 = resp.Header.Get("ETag")
+ result.ETag = resp.Header.Get("ETag")
+ result.LastModified = resp.Header.Get("Last-Modified")
+
+ if v := resp.Header.Get("x-ms-blob-committed-block-count"); v != "" {
+ i, innerErr := strconv.Atoi(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as an integer: %s", v, innerErr)
+ return
+ }
+
+ result.BlobCommittedBlockCount = int64(i)
+ }
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/blob_append_test.go b/storage/2018-03-28/blob/blobs/blob_append_test.go
new file mode 100644
index 0000000..ad1ca56
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/blob_append_test.go
@@ -0,0 +1,155 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/blob/containers"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestAppendBlobLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "append-blob.txt"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithStorageResourceManagerAuth(containersClient.Client)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Putting Append Blob..")
+ if _, err := blobClient.PutAppendBlob(ctx, accountName, containerName, fileName, PutAppendBlobInput{}); err != nil {
+ t.Fatalf("Error putting append blob: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Properties..")
+ props, err := blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error retrieving properties: %s", err)
+ }
+ if props.ContentLength != 0 {
+ t.Fatalf("Expected Content-Length to be 0 but it was %d", props.ContentLength)
+ }
+
+ t.Logf("[DEBUG] Appending First Block..")
+ appendInput := AppendBlockInput{
+ Content: []byte{
+ 12,
+ 48,
+ 93,
+ 76,
+ 29,
+ 10,
+ },
+ }
+ if _, err := blobClient.AppendBlock(ctx, accountName, containerName, fileName, appendInput); err != nil {
+ t.Fatalf("Error appending first block: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-Retrieving Properties..")
+ props, err = blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error retrieving properties: %s", err)
+ }
+ if props.ContentLength != 6 {
+ t.Fatalf("Expected Content-Length to be 6 but it was %d", props.ContentLength)
+ }
+
+ t.Logf("[DEBUG] Appending Second Block..")
+ appendInput = AppendBlockInput{
+ Content: []byte{
+ 92,
+ 62,
+ 64,
+ 47,
+ 83,
+ 77,
+ },
+ }
+ if _, err := blobClient.AppendBlock(ctx, accountName, containerName, fileName, appendInput); err != nil {
+ t.Fatalf("Error appending Second block: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-Retrieving Properties..")
+ props, err = blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error retrieving properties: %s", err)
+ }
+ if props.ContentLength != 12 {
+ t.Fatalf("Expected Content-Length to be 12 but it was %d", props.ContentLength)
+ }
+
+ t.Logf("[DEBUG] Acquiring Lease..")
+ leaseDetails, err := blobClient.AcquireLease(ctx, accountName, containerName, fileName, AcquireLeaseInput{
+ LeaseDuration: -1,
+ })
+ if err != nil {
+ t.Fatalf("Error acquiring Lease: %s", err)
+ }
+ t.Logf("[DEBUG] Lease ID is %q", leaseDetails.LeaseID)
+
+ t.Logf("[DEBUG] Appending Third Block..")
+ appendInput = AppendBlockInput{
+ Content: []byte{
+ 64,
+ 35,
+ 28,
+ 93,
+ 11,
+ 23,
+ },
+ LeaseID: &leaseDetails.LeaseID,
+ }
+ if _, err := blobClient.AppendBlock(ctx, accountName, containerName, fileName, appendInput); err != nil {
+ t.Fatalf("Error appending Third block: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-Retrieving Properties..")
+ props, err = blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{
+ LeaseID: &leaseDetails.LeaseID,
+ })
+ if err != nil {
+ t.Fatalf("Error retrieving properties: %s", err)
+ }
+ if props.ContentLength != 18 {
+ t.Fatalf("Expected Content-Length to be 18 but it was %d", props.ContentLength)
+ }
+
+ t.Logf("[DEBUG] Breaking Lease..")
+ breakLeaseInput := BreakLeaseInput{
+ LeaseID: leaseDetails.LeaseID,
+ }
+ if _, err := blobClient.BreakLease(ctx, accountName, containerName, fileName, breakLeaseInput); err != nil {
+ t.Fatalf("Error breaking lease: %s", err)
+ }
+
+ t.Logf("[DEBUG] Deleting Lease..")
+ if _, err := blobClient.Delete(ctx, accountName, containerName, fileName, DeleteInput{}); err != nil {
+ t.Fatalf("Error deleting: %s", err)
+ }
+}
diff --git a/storage/2018-03-28/blob/blobs/blob_page_test.go b/storage/2018-03-28/blob/blobs/blob_page_test.go
new file mode 100644
index 0000000..ceb58d3
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/blob_page_test.go
@@ -0,0 +1,89 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/blob/containers"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestPageBlobLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "append-blob.txt"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.StorageV2)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithStorageResourceManagerAuth(containersClient.Client)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Putting Page Blob..")
+ fileSize := int64(10240000)
+ if _, err := blobClient.PutPageBlob(ctx, accountName, containerName, fileName, PutPageBlobInput{
+ BlobContentLengthBytes: fileSize,
+ }); err != nil {
+ t.Fatalf("Error putting page blob: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Properties..")
+ props, err := blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error retrieving properties: %s", err)
+ }
+ if props.ContentLength != fileSize {
+ t.Fatalf("Expected Content-Length to be %d but it was %d", fileSize, props.ContentLength)
+ }
+
+ for iteration := 1; iteration <= 3; iteration++ {
+ t.Logf("[DEBUG] Putting Page %d of 3..", iteration)
+ byteArray := func() []byte {
+ o := make([]byte, 0)
+
+ for i := 0; i < 512; i++ {
+ o = append(o, byte(i))
+ }
+
+ return o
+ }()
+ startByte := int64(512 * iteration)
+ endByte := int64(startByte + 511)
+ putPageInput := PutPageUpdateInput{
+ StartByte: startByte,
+ EndByte: endByte,
+ Content: byteArray,
+ }
+ if _, err := blobClient.PutPageUpdate(ctx, accountName, containerName, fileName, putPageInput); err != nil {
+ t.Fatalf("Error putting page: %s", err)
+ }
+ }
+
+ t.Logf("[DEBUG] Deleting..")
+ if _, err := blobClient.Delete(ctx, accountName, containerName, fileName, DeleteInput{}); err != nil {
+ t.Fatalf("Error deleting: %s", err)
+ }
+}
diff --git a/storage/2018-03-28/blob/blobs/client.go b/storage/2018-03-28/blob/blobs/client.go
new file mode 100644
index 0000000..db20391
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/client.go
@@ -0,0 +1,25 @@
+package blobs
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for Blob Storage Blobs.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithBaseURI creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2018-03-28/blob/blobs/copy.go b/storage/2018-03-28/blob/blobs/copy.go
new file mode 100644
index 0000000..febaab5
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/copy.go
@@ -0,0 +1,235 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type CopyInput struct {
+ // Specifies the name of the source blob or file.
+ // Beginning with version 2012-02-12, this value may be a URL of up to 2 KB in length that specifies a blob.
+ // The value should be URL-encoded as it would appear in a request URI.
+ // A source blob in the same storage account can be authenticated via Shared Key.
+ // However, if the source is a blob in another account,
+ // the source blob must either be public or must be authenticated via a shared access signature.
+ // If the source blob is public, no authentication is required to perform the copy operation.
+ //
+ // Beginning with version 2015-02-21, the source object may be a file in the Azure File service.
+ // If the source object is a file that is to be copied to a blob, then the source file must be authenticated
+ // using a shared access signature, whether it resides in the same account or in a different account.
+ //
+ // Only storage accounts created on or after June 7th, 2012 allow the Copy Blob operation to
+ // copy from another storage account.
+ CopySource string
+
+ // The ID of the Lease
+ // Required if the destination blob has an active lease.
+ // The lease ID specified for this header must match the lease ID of the destination blob.
+ // If the request does not include the lease ID or it is not valid,
+ // the operation fails with status code 412 (Precondition Failed).
+ //
+ // If this header is specified and the destination blob does not currently have an active lease,
+ // the operation will also fail with status code 412 (Precondition Failed).
+ LeaseID *string
+
+ // The ID of the Lease on the Source Blob
+ // Specify to perform the Copy Blob operation only if the lease ID matches the active lease ID of the source blob.
+ SourceLeaseID *string
+
+ // For page blobs on a premium account only. Specifies the tier to be set on the target blob
+ AccessTier *AccessTier
+
+ // A user-defined name-value pair associated with the blob.
+ // If no name-value pairs are specified, the operation will copy the metadata from the source blob or
+ // file to the destination blob.
+ // If one or more name-value pairs are specified, the destination blob is created with the specified metadata,
+ // and metadata is not copied from the source blob or file.
+ MetaData map[string]string
+
+ // An ETag value.
+ // Specify an ETag value for this conditional header to copy the blob only if the specified
+ // ETag value matches the ETag value for an existing destination blob.
+ // If the ETag for the destination blob does not match the ETag specified for If-Match,
+ // the Blob service returns status code 412 (Precondition Failed).
+ IfMatch *string
+
+ // An ETag value, or the wildcard character (*).
+ // Specify an ETag value for this conditional header to copy the blob only if the specified
+ // ETag value does not match the ETag value for the destination blob.
+ // Specify the wildcard character (*) to perform the operation only if the destination blob does not exist.
+ // If the specified condition isn't met, the Blob service returns status code 412 (Precondition Failed).
+ IfNoneMatch *string
+
+ // A DateTime value.
+ // Specify this conditional header to copy the blob only if the destination blob
+ // has been modified since the specified date/time.
+ // If the destination blob has not been modified, the Blob service returns status code 412 (Precondition Failed).
+ IfModifiedSince *string
+
+ // A DateTime value.
+ // Specify this conditional header to copy the blob only if the destination blob
+ // has not been modified since the specified date/time.
+ // If the destination blob has been modified, the Blob service returns status code 412 (Precondition Failed).
+ IfUnmodifiedSince *string
+
+ // An ETag value.
+ // Specify this conditional header to copy the source blob only if its ETag matches the value specified.
+ // If the ETag values do not match, the Blob service returns status code 412 (Precondition Failed).
+ // This cannot be specified if the source is an Azure File.
+ SourceIfMatch *string
+
+ // An ETag value.
+ // Specify this conditional header to copy the blob only if its ETag does not match the value specified.
+ // If the values are identical, the Blob service returns status code 412 (Precondition Failed).
+ // This cannot be specified if the source is an Azure File.
+ SourceIfNoneMatch *string
+
+ // A DateTime value.
+ // Specify this conditional header to copy the blob only if the source blob has been modified
+ // since the specified date/time.
+ // If the source blob has not been modified, the Blob service returns status code 412 (Precondition Failed).
+ // This cannot be specified if the source is an Azure File.
+ SourceIfModifiedSince *string
+
+ // A DateTime value.
+ // Specify this conditional header to copy the blob only if the source blob has not been modified
+ // since the specified date/time.
+ // If the source blob has been modified, the Blob service returns status code 412 (Precondition Failed).
+ // This header cannot be specified if the source is an Azure File.
+ SourceIfUnmodifiedSince *string
+}
+
+type CopyResult struct {
+ autorest.Response
+
+ CopyID string
+ CopyStatus string
+}
+
+// Copy copies a blob to a destination within the storage account asynchronously.
+func (client Client) Copy(ctx context.Context, accountName, containerName, blobName string, input CopyInput) (result CopyResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "Copy", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "Copy", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "Copy", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "Copy", "`blobName` cannot be an empty string.")
+ }
+ if input.CopySource == "" {
+ return result, validation.NewError("blobs.Client", "Copy", "`input.CopySource` cannot be an empty string.")
+ }
+
+ req, err := client.CopyPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Copy", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CopySender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Copy", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CopyResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Copy", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CopyPreparer prepares the Copy request.
+func (client Client) CopyPreparer(ctx context.Context, accountName, containerName, blobName string, input CopyInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-copy-source": autorest.Encode("header", input.CopySource),
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+ if input.SourceLeaseID != nil {
+ headers["x-ms-source-lease-id"] = *input.SourceLeaseID
+ }
+ if input.AccessTier != nil {
+ headers["x-ms-access-tier"] = string(*input.AccessTier)
+ }
+
+ if input.IfMatch != nil {
+ headers["If-Match"] = *input.IfMatch
+ }
+ if input.IfNoneMatch != nil {
+ headers["If-None-Match"] = *input.IfNoneMatch
+ }
+ if input.IfUnmodifiedSince != nil {
+ headers["If-Unmodified-Since"] = *input.IfUnmodifiedSince
+ }
+ if input.IfModifiedSince != nil {
+ headers["If-Modified-Since"] = *input.IfModifiedSince
+ }
+
+ if input.SourceIfMatch != nil {
+ headers["x-ms-source-if-match"] = *input.SourceIfMatch
+ }
+ if input.SourceIfNoneMatch != nil {
+ headers["x-ms-source-if-none-match"] = *input.SourceIfNoneMatch
+ }
+ if input.SourceIfModifiedSince != nil {
+ headers["x-ms-source-if-modified-since"] = *input.SourceIfModifiedSince
+ }
+ if input.SourceIfUnmodifiedSince != nil {
+ headers["x-ms-source-if-unmodified-since"] = *input.SourceIfUnmodifiedSince
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CopySender sends the Copy request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CopySender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CopyResponder handles the response to the Copy request. The method always
+// closes the http.Response Body.
+func (client Client) CopyResponder(resp *http.Response) (result CopyResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.CopyID = resp.Header.Get("x-ms-copy-id")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/copy_abort.go b/storage/2018-03-28/blob/blobs/copy_abort.go
new file mode 100644
index 0000000..a992ff1
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/copy_abort.go
@@ -0,0 +1,110 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type AbortCopyInput struct {
+ // The Copy ID which should be aborted
+ CopyID string
+
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+}
+
+// AbortCopy aborts a pending Copy Blob operation, and leaves a destination blob with zero length and full metadata.
+func (client Client) AbortCopy(ctx context.Context, accountName, containerName, blobName string, input AbortCopyInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "AbortCopy", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "AbortCopy", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "AbortCopy", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "AbortCopy", "`blobName` cannot be an empty string.")
+ }
+ if input.CopyID == "" {
+ return result, validation.NewError("blobs.Client", "AbortCopy", "`input.CopyID` cannot be an empty string.")
+ }
+
+ req, err := client.AbortCopyPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AbortCopy", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.AbortCopySender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AbortCopy", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.AbortCopyResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AbortCopy", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// AbortCopyPreparer prepares the AbortCopy request.
+func (client Client) AbortCopyPreparer(ctx context.Context, accountName, containerName, blobName string, input AbortCopyInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "copy"),
+ "copyid": autorest.Encode("query", input.CopyID),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-copy-action": "abort",
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// AbortCopySender sends the AbortCopy request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) AbortCopySender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// AbortCopyResponder handles the response to the AbortCopy request. The method always
+// closes the http.Response Body.
+func (client Client) AbortCopyResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/copy_and_wait.go b/storage/2018-03-28/blob/blobs/copy_and_wait.go
new file mode 100644
index 0000000..a1e7fa4
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/copy_and_wait.go
@@ -0,0 +1,41 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "time"
+)
+
+// CopyAndWait copies a blob to a destination within the storage account and waits for it to finish copying.
+func (client Client) CopyAndWait(ctx context.Context, accountName, containerName, blobName string, input CopyInput, pollingInterval time.Duration) error {
+ if _, err := client.Copy(ctx, accountName, containerName, blobName, input); err != nil {
+ return fmt.Errorf("Error copying: %s", err)
+ }
+
+ for true {
+ getInput := GetPropertiesInput{
+ LeaseID: input.LeaseID,
+ }
+ getResult, err := client.GetProperties(ctx, accountName, containerName, blobName, getInput)
+ if err != nil {
+ return fmt.Errorf("")
+ }
+
+ switch getResult.CopyStatus {
+ case Aborted:
+ return fmt.Errorf("Copy was aborted: %s", getResult.CopyStatusDescription)
+
+ case Failed:
+ return fmt.Errorf("Copy failed: %s", getResult.CopyStatusDescription)
+
+ case Success:
+ return nil
+
+ case Pending:
+ time.Sleep(pollingInterval)
+ continue
+ }
+ }
+
+ return fmt.Errorf("Unexpected error waiting for the copy to complete")
+}
diff --git a/storage/2018-03-28/blob/blobs/copy_test.go b/storage/2018-03-28/blob/blobs/copy_test.go
new file mode 100644
index 0000000..6a929e7
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/copy_test.go
@@ -0,0 +1,148 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/blob/containers"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestCopyFromExistingFile(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "ubuntu.iso"
+ copiedFileName := "copied.iso"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithStorageResourceManagerAuth(containersClient.Client)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Copying file to Blob Storage..")
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+
+ refreshInterval := 5 * time.Second
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, fileName, copyInput, refreshInterval); err != nil {
+ t.Fatalf("Error copying: %s", err)
+ }
+
+ t.Logf("[DEBUG] Duplicating that file..")
+ copiedInput := CopyInput{
+ CopySource: fmt.Sprintf("%s/%s/%s", endpoints.GetBlobEndpoint(blobClient.BaseURI, accountName), containerName, fileName),
+ }
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, copiedFileName, copiedInput, refreshInterval); err != nil {
+ t.Fatalf("Error duplicating file: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Properties for the Original File..")
+ props, err := blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error getting properties for the original file: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Properties for the Copied File..")
+ copiedProps, err := blobClient.GetProperties(ctx, accountName, containerName, copiedFileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error getting properties for the copied file: %s", err)
+ }
+
+ if props.ContentLength != copiedProps.ContentLength {
+ t.Fatalf("Expected the content length to be %d but it was %d", props.ContentLength, copiedProps.ContentLength)
+ }
+
+ t.Logf("[DEBUG] Deleting copied file..")
+ if _, err := blobClient.Delete(ctx, accountName, containerName, copiedFileName, DeleteInput{}); err != nil {
+ t.Fatalf("Error deleting file: %s", err)
+ }
+
+ t.Logf("[DEBUG] Deleting original file..")
+ if _, err := blobClient.Delete(ctx, accountName, containerName, fileName, DeleteInput{}); err != nil {
+ t.Fatalf("Error deleting file: %s", err)
+ }
+}
+
+func TestCopyFromURL(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "ubuntu.iso"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithStorageResourceManagerAuth(containersClient.Client)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Copying file to Blob Storage..")
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+
+ refreshInterval := 5 * time.Second
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, fileName, copyInput, refreshInterval); err != nil {
+ t.Fatalf("Error copying: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Properties..")
+ props, err := blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error getting properties: %s", err)
+ }
+
+ if props.ContentLength == 0 {
+ t.Fatalf("Expected the file to be there but looks like it isn't: %d", props.ContentLength)
+ }
+
+ t.Logf("[DEBUG] Deleting file..")
+ if _, err := blobClient.Delete(ctx, accountName, containerName, fileName, DeleteInput{}); err != nil {
+ t.Fatalf("Error deleting file: %s", err)
+ }
+}
diff --git a/storage/2018-03-28/blob/blobs/delete.go b/storage/2018-03-28/blob/blobs/delete.go
new file mode 100644
index 0000000..c1c642d
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/delete.go
@@ -0,0 +1,105 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type DeleteInput struct {
+ // Should any Snapshots for this Blob also be deleted?
+ // If the Blob has Snapshots and this is set to False a 409 Conflict will be returned
+ DeleteSnapshots bool
+
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+}
+
+// Delete marks the specified blob or snapshot for deletion. The blob is later deleted during garbage collection.
+func (client Client) Delete(ctx context.Context, accountName, containerName, blobName string, input DeleteInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "Delete", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "Delete", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "Delete", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, containerName, blobName string, input DeleteInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ if input.DeleteSnapshots {
+ headers["x-ms-delete-snapshots"] = "include"
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/delete_snapshot.go b/storage/2018-03-28/blob/blobs/delete_snapshot.go
new file mode 100644
index 0000000..18c3d4c
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/delete_snapshot.go
@@ -0,0 +1,108 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type DeleteSnapshotInput struct {
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+
+ // The DateTime of the Snapshot which should be marked for Deletion
+ SnapshotDateTime string
+}
+
+// DeleteSnapshot marks a single Snapshot of a Blob for Deletion based on it's DateTime, which will be deleted during the next Garbage Collection cycle.
+func (client Client) DeleteSnapshot(ctx context.Context, accountName, containerName, blobName string, input DeleteSnapshotInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshot", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshot", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshot", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshot", "`blobName` cannot be an empty string.")
+ }
+ if input.SnapshotDateTime == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshot", "`input.SnapshotDateTime` cannot be an empty string.")
+ }
+
+ req, err := client.DeleteSnapshotPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "DeleteSnapshot", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSnapshotSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "DeleteSnapshot", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteSnapshotResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "DeleteSnapshot", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeleteSnapshotPreparer prepares the DeleteSnapshot request.
+func (client Client) DeleteSnapshotPreparer(ctx context.Context, accountName, containerName, blobName string, input DeleteSnapshotInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "snapshot": autorest.Encode("query", input.SnapshotDateTime),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSnapshotSender sends the DeleteSnapshot request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSnapshotSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteSnapshotResponder handles the response to the DeleteSnapshot request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteSnapshotResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/delete_snapshots.go b/storage/2018-03-28/blob/blobs/delete_snapshots.go
new file mode 100644
index 0000000..e7e2b66
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/delete_snapshots.go
@@ -0,0 +1,99 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type DeleteSnapshotsInput struct {
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+}
+
+// DeleteSnapshots marks all Snapshots of a Blob for Deletion, which will be deleted during the next Garbage Collection Cycle.
+func (client Client) DeleteSnapshots(ctx context.Context, accountName, containerName, blobName string, input DeleteSnapshotsInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshots", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshots", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshots", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshots", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.DeleteSnapshotsPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "DeleteSnapshots", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSnapshotsSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "DeleteSnapshots", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteSnapshotsResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "DeleteSnapshots", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeleteSnapshotsPreparer prepares the DeleteSnapshots request.
+func (client Client) DeleteSnapshotsPreparer(ctx context.Context, accountName, containerName, blobName string, input DeleteSnapshotsInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ // only delete the snapshots but leave the blob as-is
+ "x-ms-delete-snapshots": "only",
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSnapshotsSender sends the DeleteSnapshots request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSnapshotsSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteSnapshotsResponder handles the response to the DeleteSnapshots request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteSnapshotsResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/get.go b/storage/2018-03-28/blob/blobs/get.go
new file mode 100644
index 0000000..fa88081
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/get.go
@@ -0,0 +1,116 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetInput struct {
+ LeaseID *string
+ StartByte *int64
+ EndByte *int64
+}
+
+type GetResult struct {
+ autorest.Response
+
+ Contents []byte
+}
+
+// Get reads or downloads a blob from the system, including its metadata and properties.
+func (client Client) Get(ctx context.Context, accountName, containerName, blobName string, input GetInput) (result GetResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "Get", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "Get", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "Get", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "Get", "`blobName` cannot be an empty string.")
+ }
+ if input.LeaseID != nil && *input.LeaseID == "" {
+ return result, validation.NewError("blobs.Client", "Get", "`input.LeaseID` should either be specified or nil, not an empty string.")
+ }
+ if (input.StartByte != nil && input.EndByte == nil) || input.StartByte == nil && input.EndByte != nil {
+ return result, validation.NewError("blobs.Client", "Get", "`input.StartByte` and `input.EndByte` must both be specified, or both be nil.")
+ }
+
+ req, err := client.GetPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Get", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Get", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Get", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPreparer prepares the Get request.
+func (client Client) GetPreparer(ctx context.Context, accountName, containerName, blobName string, input GetInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.StartByte != nil && input.EndByte != nil {
+ headers["x-ms-range"] = fmt.Sprintf("bytes=%d-%d", *input.StartByte, *input.EndByte)
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetSender sends the Get request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetResponder handles the response to the Get request. The method always
+// closes the http.Response Body.
+func (client Client) GetResponder(resp *http.Response) (result GetResult, err error) {
+ if resp != nil {
+ result.Contents = make([]byte, resp.ContentLength)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusPartialContent),
+ autorest.ByUnmarshallingBytes(&result.Contents),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/get_block_list.go b/storage/2018-03-28/blob/blobs/get_block_list.go
new file mode 100644
index 0000000..9f8120c
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/get_block_list.go
@@ -0,0 +1,140 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetBlockListInput struct {
+ BlockListType BlockListType
+ LeaseID *string
+}
+
+type GetBlockListResult struct {
+ autorest.Response
+
+ // The size of the blob in bytes
+ ContentLength *int64
+
+ // The Content Type of the blob
+ ContentType string
+
+ // The ETag associated with this blob
+ ETag string
+
+ // A list of blocks which have been committed
+ CommittedBlocks CommittedBlocks `xml:"CommittedBlocks,omitempty"`
+
+ // A list of blocks which have not yet been committed
+ UncommittedBlocks UncommittedBlocks `xml:"UncommittedBlocks,omitempty"`
+}
+
+// GetBlockList retrieves the list of blocks that have been uploaded as part of a block blob.
+func (client Client) GetBlockList(ctx context.Context, accountName, containerName, blobName string, input GetBlockListInput) (result GetBlockListResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "GetBlockList", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "GetBlockList", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "GetBlockList", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "GetBlockList", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.GetBlockListPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetBlockList", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetBlockListSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetBlockList", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetBlockListResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetBlockList", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetBlockListPreparer prepares the GetBlockList request.
+func (client Client) GetBlockListPreparer(ctx context.Context, accountName, containerName, blobName string, input GetBlockListInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "blocklisttype": autorest.Encode("query", string(input.BlockListType)),
+ "comp": autorest.Encode("query", "blocklist"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetBlockListSender sends the GetBlockList request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetBlockListSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetBlockListResponder handles the response to the GetBlockList request. The method always
+// closes the http.Response Body.
+func (client Client) GetBlockListResponder(resp *http.Response) (result GetBlockListResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.ContentType = resp.Header.Get("Content-Type")
+ result.ETag = resp.Header.Get("ETag")
+
+ if v := resp.Header.Get("x-ms-blob-content-length"); v != "" {
+ i, innerErr := strconv.Atoi(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as an integer: %s", v, innerErr)
+ return
+ }
+
+ i64 := int64(i)
+ result.ContentLength = &i64
+ }
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/get_page_ranges.go b/storage/2018-03-28/blob/blobs/get_page_ranges.go
new file mode 100644
index 0000000..37abf63
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/get_page_ranges.go
@@ -0,0 +1,152 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetPageRangesInput struct {
+ LeaseID *string
+
+ StartByte *int64
+ EndByte *int64
+}
+
+type GetPageRangesResult struct {
+ autorest.Response
+
+ // The size of the blob in bytes
+ ContentLength *int64
+
+ // The Content Type of the blob
+ ContentType string
+
+ // The ETag associated with this blob
+ ETag string
+
+ PageRanges []PageRange `xml:"PageRange"`
+}
+
+type PageRange struct {
+ // The start byte offset for this range, inclusive
+ Start int64 `xml:"Start"`
+
+ // The end byte offset for this range, inclusive
+ End int64 `xml:"End"`
+}
+
+// GetPageRanges returns the list of valid page ranges for a page blob or snapshot of a page blob.
+func (client Client) GetPageRanges(ctx context.Context, accountName, containerName, blobName string, input GetPageRangesInput) (result GetPageRangesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "GetPageRanges", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "GetPageRanges", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "GetPageRanges", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "GetPageRanges", "`blobName` cannot be an empty string.")
+ }
+ if (input.StartByte != nil && input.EndByte == nil) || input.StartByte == nil && input.EndByte != nil {
+ return result, validation.NewError("blobs.Client", "GetPageRanges", "`input.StartByte` and `input.EndByte` must both be specified, or both be nil.")
+ }
+
+ req, err := client.GetPageRangesPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetPageRanges", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetPageRangesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetPageRanges", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetPageRangesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetPageRanges", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPageRangesPreparer prepares the GetPageRanges request.
+func (client Client) GetPageRangesPreparer(ctx context.Context, accountName, containerName, blobName string, input GetPageRangesInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "pagelist"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ if input.StartByte != nil && input.EndByte != nil {
+ headers["x-ms-range"] = fmt.Sprintf("bytes=%d-%d", *input.StartByte, *input.EndByte)
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetPageRangesSender sends the GetPageRanges request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetPageRangesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetPageRangesResponder handles the response to the GetPageRanges request. The method always
+// closes the http.Response Body.
+func (client Client) GetPageRangesResponder(resp *http.Response) (result GetPageRangesResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.ContentType = resp.Header.Get("Content-Type")
+ result.ETag = resp.Header.Get("ETag")
+
+ if v := resp.Header.Get("x-ms-blob-content-length"); v != "" {
+ i, innerErr := strconv.Atoi(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as an integer: %s", v, innerErr)
+ return
+ }
+
+ i64 := int64(i)
+ result.ContentLength = &i64
+ }
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/incremental_copy_blob.go b/storage/2018-03-28/blob/blobs/incremental_copy_blob.go
new file mode 100644
index 0000000..7fb7e6b
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/incremental_copy_blob.go
@@ -0,0 +1,120 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type IncrementalCopyBlobInput struct {
+ CopySource string
+ IfModifiedSince *string
+ IfUnmodifiedSince *string
+ IfMatch *string
+ IfNoneMatch *string
+}
+
+// IncrementalCopyBlob copies a snapshot of the source page blob to a destination page blob.
+// The snapshot is copied such that only the differential changes between the previously copied
+// snapshot are transferred to the destination.
+// The copied snapshots are complete copies of the original snapshot and can be read or copied from as usual.
+func (client Client) IncrementalCopyBlob(ctx context.Context, accountName, containerName, blobName string, input IncrementalCopyBlobInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "IncrementalCopyBlob", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "IncrementalCopyBlob", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "IncrementalCopyBlob", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "IncrementalCopyBlob", "`blobName` cannot be an empty string.")
+ }
+ if input.CopySource == "" {
+ return result, validation.NewError("blobs.Client", "IncrementalCopyBlob", "`input.CopySource` cannot be an empty string.")
+ }
+
+ req, err := client.IncrementalCopyBlobPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "IncrementalCopyBlob", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.IncrementalCopyBlobSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "IncrementalCopyBlob", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.IncrementalCopyBlobResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "IncrementalCopyBlob", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// IncrementalCopyBlobPreparer prepares the IncrementalCopyBlob request.
+func (client Client) IncrementalCopyBlobPreparer(ctx context.Context, accountName, containerName, blobName string, input IncrementalCopyBlobInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "incrementalcopy"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-copy-source": input.CopySource,
+ }
+
+ if input.IfModifiedSince != nil {
+ headers["If-Modified-Since"] = *input.IfModifiedSince
+ }
+ if input.IfUnmodifiedSince != nil {
+ headers["If-Unmodified-Since"] = *input.IfUnmodifiedSince
+ }
+ if input.IfMatch != nil {
+ headers["If-Match"] = *input.IfMatch
+ }
+ if input.IfNoneMatch != nil {
+ headers["If-None-Match"] = *input.IfNoneMatch
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// IncrementalCopyBlobSender sends the IncrementalCopyBlob request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) IncrementalCopyBlobSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// IncrementalCopyBlobResponder handles the response to the IncrementalCopyBlob request. The method always
+// closes the http.Response Body.
+func (client Client) IncrementalCopyBlobResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/lease_acquire.go b/storage/2018-03-28/blob/blobs/lease_acquire.go
new file mode 100644
index 0000000..432c1f5
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/lease_acquire.go
@@ -0,0 +1,135 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type AcquireLeaseInput struct {
+ // The ID of the existing Lease, if leased
+ LeaseID *string
+
+ // Specifies the duration of the lease, in seconds, or negative one (-1) for a lease that never expires.
+ // A non-infinite lease can be between 15 and 60 seconds
+ LeaseDuration int
+
+ // The Proposed new ID for the Lease
+ ProposedLeaseID *string
+}
+
+type AcquireLeaseResult struct {
+ autorest.Response
+
+ LeaseID string
+}
+
+// AcquireLease establishes and manages a lock on a blob for write and delete operations.
+func (client Client) AcquireLease(ctx context.Context, accountName, containerName, blobName string, input AcquireLeaseInput) (result AcquireLeaseResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`blobName` cannot be an empty string.")
+ }
+ if input.LeaseID != nil && *input.LeaseID == "" {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`input.LeaseID` cannot be an empty string, if specified.")
+ }
+ if input.ProposedLeaseID != nil && *input.ProposedLeaseID == "" {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`input.ProposedLeaseID` cannot be an empty string, if specified.")
+ }
+ // An infinite lease duration is -1 seconds. A non-infinite lease can be between 15 and 60 seconds
+ if input.LeaseDuration != -1 && (input.LeaseDuration <= 15 || input.LeaseDuration >= 60) {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`input.LeaseDuration` must be -1 (infinite), or between 15 and 60 seconds.")
+ }
+
+ req, err := client.AcquireLeasePreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AcquireLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.AcquireLeaseSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AcquireLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.AcquireLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AcquireLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// AcquireLeasePreparer prepares the AcquireLease request.
+func (client Client) AcquireLeasePreparer(ctx context.Context, accountName, containerName, blobName string, input AcquireLeaseInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "acquire",
+ "x-ms-lease-duration": input.LeaseDuration,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ if input.ProposedLeaseID != nil {
+ headers["x-ms-proposed-lease-id"] = input.ProposedLeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// AcquireLeaseSender sends the AcquireLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) AcquireLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// AcquireLeaseResponder handles the response to the AcquireLease request. The method always
+// closes the http.Response Body.
+func (client Client) AcquireLeaseResponder(resp *http.Response) (result AcquireLeaseResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.LeaseID = resp.Header.Get("x-ms-lease-id")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/lease_break.go b/storage/2018-03-28/blob/blobs/lease_break.go
new file mode 100644
index 0000000..d564204
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/lease_break.go
@@ -0,0 +1,124 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type BreakLeaseInput struct {
+ // For a break operation, proposed duration the lease should continue
+ // before it is broken, in seconds, between 0 and 60.
+ // This break period is only used if it is shorter than the time remaining on the lease.
+ // If longer, the time remaining on the lease is used.
+ // A new lease will not be available before the break period has expired,
+ // but the lease may be held for longer than the break period.
+ // If this header does not appear with a break operation, a fixed-duration lease breaks
+ // after the remaining lease period elapses, and an infinite lease breaks immediately.
+ BreakPeriod *int
+
+ LeaseID string
+}
+
+type BreakLeaseResponse struct {
+ autorest.Response
+
+ // Approximate time remaining in the lease period, in seconds.
+ // If the break is immediate, 0 is returned.
+ LeaseTime int
+}
+
+// BreakLease breaks an existing lock on a blob using the LeaseID.
+func (client Client) BreakLease(ctx context.Context, accountName, containerName, blobName string, input BreakLeaseInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "BreakLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "BreakLease", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "BreakLease", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "BreakLease", "`blobName` cannot be an empty string.")
+ }
+ if input.LeaseID == "" {
+ return result, validation.NewError("blobs.Client", "BreakLease", "`input.LeaseID` cannot be an empty string.")
+ }
+
+ req, err := client.BreakLeasePreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "BreakLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.BreakLeaseSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "BreakLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.BreakLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "BreakLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// BreakLeasePreparer prepares the BreakLease request.
+func (client Client) BreakLeasePreparer(ctx context.Context, accountName, containerName, blobName string, input BreakLeaseInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "break",
+ "x-ms-lease-id": input.LeaseID,
+ }
+
+ if input.BreakPeriod != nil {
+ headers["x-ms-lease-break-period"] = *input.BreakPeriod
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// BreakLeaseSender sends the BreakLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) BreakLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// BreakLeaseResponder handles the response to the BreakLease request. The method always
+// closes the http.Response Body.
+func (client Client) BreakLeaseResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/lease_change.go b/storage/2018-03-28/blob/blobs/lease_change.go
new file mode 100644
index 0000000..c57f9db
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/lease_change.go
@@ -0,0 +1,117 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type ChangeLeaseInput struct {
+ ExistingLeaseID string
+ ProposedLeaseID string
+}
+
+type ChangeLeaseResponse struct {
+ autorest.Response
+
+ LeaseID string
+}
+
+// ChangeLease changes an existing lock on a blob for another lock.
+func (client Client) ChangeLease(ctx context.Context, accountName, containerName, blobName string, input ChangeLeaseInput) (result ChangeLeaseResponse, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "ChangeLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "ChangeLease", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "ChangeLease", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "ChangeLease", "`blobName` cannot be an empty string.")
+ }
+ if input.ExistingLeaseID == "" {
+ return result, validation.NewError("blobs.Client", "ChangeLease", "`input.ExistingLeaseID` cannot be an empty string.")
+ }
+ if input.ProposedLeaseID == "" {
+ return result, validation.NewError("blobs.Client", "ChangeLease", "`input.ProposedLeaseID` cannot be an empty string.")
+ }
+
+ req, err := client.ChangeLeasePreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "ChangeLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ChangeLeaseSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "ChangeLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ChangeLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "ChangeLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ChangeLeasePreparer prepares the ChangeLease request.
+func (client Client) ChangeLeasePreparer(ctx context.Context, accountName, containerName, blobName string, input ChangeLeaseInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "change",
+ "x-ms-lease-id": input.ExistingLeaseID,
+ "x-ms-proposed-lease-id": input.ProposedLeaseID,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ChangeLeaseSender sends the ChangeLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ChangeLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ChangeLeaseResponder handles the response to the ChangeLease request. The method always
+// closes the http.Response Body.
+func (client Client) ChangeLeaseResponder(resp *http.Response) (result ChangeLeaseResponse, err error) {
+ if resp != nil && resp.Header != nil {
+ result.LeaseID = resp.Header.Get("x-ms-lease-id")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/lease_release.go b/storage/2018-03-28/blob/blobs/lease_release.go
new file mode 100644
index 0000000..0226cdf
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/lease_release.go
@@ -0,0 +1,98 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// ReleaseLease releases a lock based on the Lease ID.
+func (client Client) ReleaseLease(ctx context.Context, accountName, containerName, blobName, leaseID string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "ReleaseLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "ReleaseLease", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "ReleaseLease", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "ReleaseLease", "`blobName` cannot be an empty string.")
+ }
+ if leaseID == "" {
+ return result, validation.NewError("blobs.Client", "ReleaseLease", "`leaseID` cannot be an empty string.")
+ }
+
+ req, err := client.ReleaseLeasePreparer(ctx, accountName, containerName, blobName, leaseID)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "ReleaseLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ReleaseLeaseSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "ReleaseLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ReleaseLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "ReleaseLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ReleaseLeasePreparer prepares the ReleaseLease request.
+func (client Client) ReleaseLeasePreparer(ctx context.Context, accountName, containerName, blobName, leaseID string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "release",
+ "x-ms-lease-id": leaseID,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ReleaseLeaseSender sends the ReleaseLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ReleaseLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ReleaseLeaseResponder handles the response to the ReleaseLease request. The method always
+// closes the http.Response Body.
+func (client Client) ReleaseLeaseResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/lease_renew.go b/storage/2018-03-28/blob/blobs/lease_renew.go
new file mode 100644
index 0000000..69c495b
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/lease_renew.go
@@ -0,0 +1,97 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+func (client Client) RenewLease(ctx context.Context, accountName, containerName, blobName, leaseID string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "RenewLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "RenewLease", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "RenewLease", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "RenewLease", "`blobName` cannot be an empty string.")
+ }
+ if leaseID == "" {
+ return result, validation.NewError("blobs.Client", "RenewLease", "`leaseID` cannot be an empty string.")
+ }
+
+ req, err := client.RenewLeasePreparer(ctx, accountName, containerName, blobName, leaseID)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "RenewLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.RenewLeaseSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "RenewLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.RenewLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "RenewLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// RenewLeasePreparer prepares the RenewLease request.
+func (client Client) RenewLeasePreparer(ctx context.Context, accountName, containerName, blobName, leaseID string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "renew",
+ "x-ms-lease-id": leaseID,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// RenewLeaseSender sends the RenewLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) RenewLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// RenewLeaseResponder handles the response to the RenewLease request. The method always
+// closes the http.Response Body.
+func (client Client) RenewLeaseResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/lease_test.go b/storage/2018-03-28/blob/blobs/lease_test.go
new file mode 100644
index 0000000..7196f3a
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/lease_test.go
@@ -0,0 +1,106 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/blob/containers"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestLeaseLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "ubuntu.iso"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithStorageResourceManagerAuth(containersClient.Client)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Copying file to Blob Storage..")
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+
+ refreshInterval := 5 * time.Second
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, fileName, copyInput, refreshInterval); err != nil {
+ t.Fatalf("Error copying: %s", err)
+ }
+ defer blobClient.Delete(ctx, accountName, containerName, fileName, DeleteInput{})
+
+ // Test begins here
+ t.Logf("[DEBUG] Acquiring Lease..")
+ leaseInput := AcquireLeaseInput{
+ LeaseDuration: -1,
+ }
+ leaseInfo, err := blobClient.AcquireLease(ctx, accountName, containerName, fileName, leaseInput)
+ if err != nil {
+ t.Fatalf("Error acquiring lease: %s", err)
+ }
+ t.Logf("[DEBUG] Lease ID: %q", leaseInfo.LeaseID)
+
+ t.Logf("[DEBUG] Changing Lease..")
+ changeLeaseInput := ChangeLeaseInput{
+ ExistingLeaseID: leaseInfo.LeaseID,
+ ProposedLeaseID: "31f5bb01-cdd9-4166-bcdc-95186076bde0",
+ }
+ changeLeaseResult, err := blobClient.ChangeLease(ctx, accountName, containerName, fileName, changeLeaseInput)
+ if err != nil {
+ t.Fatalf("Error changing lease: %s", err)
+ }
+ t.Logf("[DEBUG] New Lease ID: %q", changeLeaseResult.LeaseID)
+
+ t.Logf("[DEBUG] Releasing Lease..")
+ if _, err := blobClient.ReleaseLease(ctx, accountName, containerName, fileName, changeLeaseResult.LeaseID); err != nil {
+ t.Fatalf("Error releasing lease: %s", err)
+ }
+
+ t.Logf("[DEBUG] Acquiring a new lease..")
+ leaseInput = AcquireLeaseInput{
+ LeaseDuration: 30,
+ }
+ leaseInfo, err = blobClient.AcquireLease(ctx, accountName, containerName, fileName, leaseInput)
+ if err != nil {
+ t.Fatalf("Error acquiring lease: %s", err)
+ }
+ t.Logf("[DEBUG] Lease ID: %q", leaseInfo.LeaseID)
+
+ t.Logf("[DEBUG] Renewing lease..")
+ if _, err := blobClient.RenewLease(ctx, accountName, containerName, fileName, leaseInfo.LeaseID); err != nil {
+ t.Fatalf("Error renewing lease: %s", err)
+ }
+
+ t.Logf("[DEBUG] Breaking lease..")
+ breakLeaseInput := BreakLeaseInput{
+ LeaseID: leaseInfo.LeaseID,
+ }
+ if _, err := blobClient.BreakLease(ctx, accountName, containerName, fileName, breakLeaseInput); err != nil {
+ t.Fatalf("Error breaking lease: %s", err)
+ }
+}
diff --git a/storage/2018-03-28/blob/blobs/lifecycle_test.go b/storage/2018-03-28/blob/blobs/lifecycle_test.go
new file mode 100644
index 0000000..e0b326e
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/lifecycle_test.go
@@ -0,0 +1,158 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/blob/containers"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "example.txt"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithAuthorizer(containersClient.Client, storageAuth)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Copying file to Blob Storage..")
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+
+ refreshInterval := 5 * time.Second
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, fileName, copyInput, refreshInterval); err != nil {
+ t.Fatalf("Error copying: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Blob Properties..")
+ details, err := blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error retrieving properties: %s", err)
+ }
+
+ // default value
+ if details.AccessTier != Hot {
+ t.Fatalf("Expected the AccessTier to be %q but got %q", Hot, details.AccessTier)
+ }
+ if details.BlobType != BlockBlob {
+ t.Fatalf("Expected BlobType to be %q but got %q", BlockBlob, details.BlobType)
+ }
+ if len(details.MetaData) != 0 {
+ t.Fatalf("Expected there to be no items of metadata but got %d", len(details.MetaData))
+ }
+
+ t.Logf("[DEBUG] Checking it's returned in the List API..")
+ listInput := containers.ListBlobsInput{}
+ listResult, err := containersClient.ListBlobs(ctx, accountName, containerName, listInput)
+ if err != nil {
+ t.Fatalf("Error listing blobs: %s", err)
+ }
+
+ if len(listResult.Blobs.Blobs) != 1 {
+ t.Fatalf("Expected there to be 1 blob in the container but got %d", len(listResult.Blobs.Blobs))
+ }
+
+ t.Logf("[DEBUG] Setting MetaData..")
+ metaDataInput := SetMetaDataInput{
+ MetaData: map[string]string{
+ "hello": "there",
+ },
+ }
+ if _, err := blobClient.SetMetaData(ctx, accountName, containerName, fileName, metaDataInput); err != nil {
+ t.Fatalf("Error setting MetaData: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-retrieving Blob Properties..")
+ details, err = blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error re-retrieving properties: %s", err)
+ }
+
+ // default value
+ if details.AccessTier != Hot {
+ t.Fatalf("Expected the AccessTier to be %q but got %q", Hot, details.AccessTier)
+ }
+ if details.BlobType != BlockBlob {
+ t.Fatalf("Expected BlobType to be %q but got %q", BlockBlob, details.BlobType)
+ }
+ if len(details.MetaData) != 1 {
+ t.Fatalf("Expected there to be 1 item of metadata but got %d", len(details.MetaData))
+ }
+ if details.MetaData["hello"] != "there" {
+ t.Fatalf("Expected `hello` to be `there` but got %q", details.MetaData["there"])
+ }
+
+ t.Logf("[DEBUG] Retrieving the Block List..")
+ getBlockListInput := GetBlockListInput{
+ BlockListType: All,
+ }
+ blockList, err := blobClient.GetBlockList(ctx, accountName, containerName, fileName, getBlockListInput)
+ if err != nil {
+ t.Fatalf("Error retrieving Block List: %s", err)
+ }
+
+ // since this is a copy from an existing file, all blocks should be present
+ if len(blockList.CommittedBlocks.Blocks) == 0 {
+ t.Fatalf("Expected there to be committed blocks but there weren't!")
+ }
+ if len(blockList.UncommittedBlocks.Blocks) != 0 {
+ t.Fatalf("Expected all blocks to be committed but got %d uncommitted blocks", len(blockList.UncommittedBlocks.Blocks))
+ }
+
+ t.Logf("[DEBUG] Changing the Access Tiers..")
+ tiers := []AccessTier{
+ Hot,
+ Cool,
+ Archive,
+ }
+ for _, tier := range tiers {
+ t.Logf("[DEBUG] Updating the Access Tier to %q..", string(tier))
+ if _, err := blobClient.SetTier(ctx, accountName, containerName, fileName, tier); err != nil {
+ t.Fatalf("Error setting the Access Tier: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-retrieving Blob Properties..")
+ details, err = blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error re-retrieving properties: %s", err)
+ }
+
+ if details.AccessTier != tier {
+ t.Fatalf("Expected the AccessTier to be %q but got %q", tier, details.AccessTier)
+ }
+ }
+
+ t.Logf("[DEBUG] Deleting Blob")
+ if _, err := blobClient.Delete(ctx, accountName, containerName, fileName, DeleteInput{}); err != nil {
+ t.Fatalf("Error deleting Blob: %s", err)
+ }
+}
diff --git a/storage/2018-03-28/blob/blobs/metadata_set.go b/storage/2018-03-28/blob/blobs/metadata_set.go
new file mode 100644
index 0000000..ec69152
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/metadata_set.go
@@ -0,0 +1,113 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type SetMetaDataInput struct {
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+
+ // Any metadata which should be added to this blob
+ MetaData map[string]string
+}
+
+// SetMetaData marks the specified blob or snapshot for deletion. The blob is later deleted during garbage collection.
+func (client Client) SetMetaData(ctx context.Context, accountName, containerName, blobName string, input SetMetaDataInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`blobName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("blobs.Client", "GetProperties", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.SetMetaDataPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetMetaDataSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetMetaDataPreparer prepares the SetMetaData request.
+func (client Client) SetMetaDataPreparer(ctx context.Context, accountName, containerName, blobName string, input SetMetaDataInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetMetaDataSender sends the SetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetMetaDataResponder handles the response to the SetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) SetMetaDataResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/models.go b/storage/2018-03-28/blob/blobs/models.go
new file mode 100644
index 0000000..d7d83aa
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/models.go
@@ -0,0 +1,82 @@
+package blobs
+
+type AccessTier string
+
+var (
+ Archive AccessTier = "Archive"
+ Cool AccessTier = "Cool"
+ Hot AccessTier = "Hot"
+)
+
+type ArchiveStatus string
+
+var (
+ None ArchiveStatus = ""
+ RehydratePendingToCool ArchiveStatus = "rehydrate-pending-to-cool"
+ RehydratePendingToHot ArchiveStatus = "rehydrate-pending-to-hot"
+)
+
+type BlockListType string
+
+var (
+ All BlockListType = "all"
+ Committed BlockListType = "committed"
+ Uncommitted BlockListType = "uncommitted"
+)
+
+type Block struct {
+ // The base64-encoded Block ID
+ Name string `xml:"Name"`
+
+ // The size of the Block in Bytes
+ Size int64 `xml:"Size"`
+}
+
+type BlobType string
+
+var (
+ AppendBlob BlobType = "AppendBlob"
+ BlockBlob BlobType = "BlockBlob"
+ PageBlob BlobType = "PageBlob"
+)
+
+type CommittedBlocks struct {
+ Blocks []Block `xml:"Block"`
+}
+
+type CopyStatus string
+
+var (
+ Aborted CopyStatus = "aborted"
+ Failed CopyStatus = "failed"
+ Pending CopyStatus = "pending"
+ Success CopyStatus = "success"
+)
+
+type LeaseDuration string
+
+var (
+ Fixed LeaseDuration = "fixed"
+ Infinite LeaseDuration = "infinite"
+)
+
+type LeaseState string
+
+var (
+ Available LeaseState = "available"
+ Breaking LeaseState = "breaking"
+ Broken LeaseState = "broken"
+ Expired LeaseState = "expired"
+ Leased LeaseState = "leased"
+)
+
+type LeaseStatus string
+
+var (
+ Locked LeaseStatus = "locked"
+ Unlocked LeaseStatus = "unlocked"
+)
+
+type UncommittedBlocks struct {
+ Blocks []Block `xml:"Block"`
+}
diff --git a/storage/2018-03-28/blob/blobs/properties_get.go b/storage/2018-03-28/blob/blobs/properties_get.go
new file mode 100644
index 0000000..de7c5fc
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/properties_get.go
@@ -0,0 +1,310 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetPropertiesInput struct {
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+}
+
+type GetPropertiesResult struct {
+ autorest.Response
+
+ // The tier of page blob on a premium storage account or tier of block blob on blob storage or general purpose v2 account.
+ AccessTier AccessTier
+
+ // This gives the last time tier was changed on the object.
+ // This header is returned only if tier on block blob was ever set.
+ // The date format follows RFC 1123
+ AccessTierChangeTime string
+
+ // For page blobs on a premium storage account only.
+ // If the access tier is not explicitly set on the blob, the tier is inferred based on its content length
+ // and this header will be returned with true value.
+ // For block blobs on Blob Storage or general purpose v2 account, if the blob does not have the access tier
+ // set then we infer the tier from the storage account properties. This header is set only if the block blob
+ // tier is inferred
+ AccessTierInferred bool
+
+ // For blob storage or general purpose v2 account.
+ // If the blob is being rehydrated and is not complete then this header is returned indicating
+ // that rehydrate is pending and also tells the destination tier
+ ArchiveStatus ArchiveStatus
+
+ // The number of committed blocks present in the blob.
+ // This header is returned only for append blobs.
+ BlobCommittedBlockCount string
+
+ // The current sequence number for a page blob.
+ // This header is not returned for block blobs or append blobs.
+ // This header is not returned for block blobs.
+ BlobSequenceNumber string
+
+ // The blob type.
+ BlobType BlobType
+
+ // If the Cache-Control request header has previously been set for the blob, that value is returned in this header.
+ CacheControl string
+
+ // The Content-Disposition response header field conveys additional information about how to process
+ // the response payload, and also can be used to attach additional metadata.
+ // For example, if set to attachment, it indicates that the user-agent should not display the response,
+ // but instead show a Save As dialog.
+ ContentDisposition string
+
+ // If the Content-Encoding request header has previously been set for the blob,
+ // that value is returned in this header.
+ ContentEncoding string
+
+ // If the Content-Language request header has previously been set for the blob,
+ // that value is returned in this header.
+ ContentLanguage string
+
+ // The size of the blob in bytes.
+ // For a page blob, this header returns the value of the x-ms-blob-content-length header stored with the blob.
+ ContentLength int64
+
+ // The content type specified for the blob.
+ // If no content type was specified, the default content type is `application/octet-stream`.
+ ContentType string
+
+ // If the Content-MD5 header has been set for the blob, this response header is returned so that
+ // the client can check for message content integrity.
+ ContentMD5 string
+
+ // Conclusion time of the last attempted Copy Blob operation where this blob was the destination blob.
+ // This value can specify the time of a completed, aborted, or failed copy attempt.
+ // This header does not appear if a copy is pending, if this blob has never been the
+ // destination in a Copy Blob operation, or if this blob has been modified after a concluded Copy Blob
+ // operation using Set Blob Properties, Put Blob, or Put Block List.
+ CopyCompletionTime string
+
+ // Included if the blob is incremental copy blob or incremental copy snapshot, if x-ms-copy-status is success.
+ // Snapshot time of the last successful incremental copy snapshot for this blob
+ CopyDestinationSnapshot string
+
+ // String identifier for the last attempted Copy Blob operation where this blob was the destination blob.
+ // This header does not appear if this blob has never been the destination in a Copy Blob operation,
+ // or if this blob has been modified after a concluded Copy Blob operation using Set Blob Properties,
+ // Put Blob, or Put Block List.
+ CopyID string
+
+ // Contains the number of bytes copied and the total bytes in the source in the last attempted
+ // Copy Blob operation where this blob was the destination blob.
+ // Can show between 0 and Content-Length bytes copied.
+ // This header does not appear if this blob has never been the destination in a Copy Blob operation,
+ // or if this blob has been modified after a concluded Copy Blob operation using Set Blob Properties,
+ // Put Blob, or Put Block List.
+ CopyProgress string
+
+ // URL up to 2 KB in length that specifies the source blob used in the last attempted Copy Blob operation
+ // where this blob was the destination blob.
+ // This header does not appear if this blob has never been the destination in a Copy Blob operation,
+ // or if this blob has been modified after a concluded Copy Blob operation using Set Blob Properties,
+ // Put Blob, or Put Block List
+ CopySource string
+
+ // State of the copy operation identified by x-ms-copy-id, with these values:
+ // - success: Copy completed successfully.
+ // - pending: Copy is in progress.
+ // Check x-ms-copy-status-description if intermittent, non-fatal errors
+ // impede copy progress but don’t cause failure.
+ // - aborted: Copy was ended by Abort Copy Blob.
+ // - failed: Copy failed. See x-ms- copy-status-description for failure details.
+ // This header does not appear if this blob has never been the destination in a Copy Blob operation,
+ // or if this blob has been modified after a completed Copy Blob operation using Set Blob Properties,
+ // Put Blob, or Put Block List.
+ CopyStatus CopyStatus
+
+ // Describes cause of fatal or non-fatal copy operation failure.
+ // This header does not appear if this blob has never been the destination in a Copy Blob operation,
+ // or if this blob has been modified after a concluded Copy Blob operation using Set Blob Properties,
+ // Put Blob, or Put Block List.
+ CopyStatusDescription string
+
+ // The date/time at which the blob was created. The date format follows RFC 1123
+ CreationTime string
+
+ // The ETag contains a value that you can use to perform operations conditionally
+ ETag string
+
+ // Included if the blob is incremental copy blob.
+ IncrementalCopy bool
+
+ // The date/time that the blob was last modified. The date format follows RFC 1123.
+ LastModified string
+
+ // When a blob is leased, specifies whether the lease is of infinite or fixed duration
+ LeaseDuration LeaseDuration
+
+ // The lease state of the blob
+ LeaseState LeaseState
+
+ LeaseStatus LeaseStatus
+
+ // A set of name-value pairs that correspond to the user-defined metadata associated with this blob
+ MetaData map[string]string
+
+ // Is the Storage Account encrypted using server-side encryption? This should always return true
+ ServerEncrypted bool
+}
+
+// GetProperties returns all user-defined metadata, standard HTTP properties, and system properties for the blob
+func (client Client) GetProperties(ctx context.Context, accountName, containerName, blobName string, input GetPropertiesInput) (result GetPropertiesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.GetPropertiesPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetPropertiesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPropertiesPreparer prepares the GetProperties request.
+func (client Client) GetPropertiesPreparer(ctx context.Context, accountName, containerName, blobName string, input GetPropertiesInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsHead(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetPropertiesSender sends the GetProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetPropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetPropertiesResponder handles the response to the GetProperties request. The method always
+// closes the http.Response Body.
+func (client Client) GetPropertiesResponder(resp *http.Response) (result GetPropertiesResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.AccessTier = AccessTier(resp.Header.Get("x-ms-access-tier"))
+ result.AccessTierChangeTime = resp.Header.Get(" x-ms-access-tier-change-time")
+ result.ArchiveStatus = ArchiveStatus(resp.Header.Get(" x-ms-archive-status"))
+ result.BlobCommittedBlockCount = resp.Header.Get("x-ms-blob-committed-block-count")
+ result.BlobSequenceNumber = resp.Header.Get("x-ms-blob-sequence-number")
+ result.BlobType = BlobType(resp.Header.Get("x-ms-blob-type"))
+ result.CacheControl = resp.Header.Get("Cache-Control")
+ result.ContentDisposition = resp.Header.Get("Content-Disposition")
+ result.ContentEncoding = resp.Header.Get("Content-Encoding")
+ result.ContentLanguage = resp.Header.Get("Content-Language")
+ result.ContentMD5 = resp.Header.Get("Content-MD5")
+ result.ContentType = resp.Header.Get("Content-Type")
+ result.CopyCompletionTime = resp.Header.Get("x-ms-copy-completion-time")
+ result.CopyDestinationSnapshot = resp.Header.Get("x-ms-copy-destination-snapshot")
+ result.CopyID = resp.Header.Get("x-ms-copy-id")
+ result.CopyProgress = resp.Header.Get(" x-ms-copy-progress")
+ result.CopySource = resp.Header.Get("x-ms-copy-source")
+ result.CopyStatus = CopyStatus(resp.Header.Get("x-ms-copy-status"))
+ result.CopyStatusDescription = resp.Header.Get("x-ms-copy-status-description")
+ result.CreationTime = resp.Header.Get("x-ms-creation-time")
+ result.ETag = resp.Header.Get("Etag")
+ result.LastModified = resp.Header.Get("Last-Modified")
+ result.LeaseDuration = LeaseDuration(resp.Header.Get("x-ms-lease-duration"))
+ result.LeaseState = LeaseState(resp.Header.Get("x-ms-lease-state"))
+ result.LeaseStatus = LeaseStatus(resp.Header.Get("x-ms-lease-status"))
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+
+ if v := resp.Header.Get("x-ms-access-tier-inferred"); v != "" {
+ b, innerErr := strconv.ParseBool(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as a bool: %s", v, innerErr)
+ return
+ }
+
+ result.AccessTierInferred = b
+ }
+
+ if v := resp.Header.Get("Content-Length"); v != "" {
+ i, innerErr := strconv.Atoi(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as an integer: %s", v, innerErr)
+ }
+
+ result.ContentLength = int64(i)
+ }
+
+ if v := resp.Header.Get("x-ms-incremental-copy"); v != "" {
+ b, innerErr := strconv.ParseBool(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as a bool: %s", v, innerErr)
+ return
+ }
+
+ result.IncrementalCopy = b
+ }
+
+ if v := resp.Header.Get("x-ms-server-encrypted"); v != "" {
+ b, innerErr := strconv.ParseBool(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as a bool: %s", v, innerErr)
+ return
+ }
+
+ result.IncrementalCopy = b
+ }
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/properties_set.go b/storage/2018-03-28/blob/blobs/properties_set.go
new file mode 100644
index 0000000..a8c0ed8
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/properties_set.go
@@ -0,0 +1,156 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type SetPropertiesInput struct {
+ CacheControl *string
+ ContentType *string
+ ContentMD5 *string
+ ContentEncoding *string
+ ContentLanguage *string
+ LeaseID *string
+ ContentDisposition *string
+ ContentLength *int64
+ SequenceNumberAction *SequenceNumberAction
+ BlobSequenceNumber *string
+}
+
+type SetPropertiesResult struct {
+ autorest.Response
+
+ BlobSequenceNumber string
+ Etag string
+}
+
+// SetProperties sets system properties on the blob.
+func (client Client) SetProperties(ctx context.Context, accountName, containerName, blobName string, input SetPropertiesInput) (result SetPropertiesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "SetProperties", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "SetProperties", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "SetProperties", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "SetProperties", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.SetPropertiesPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetPropertiesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+type SequenceNumberAction string
+
+var (
+ Increment SequenceNumberAction = "increment"
+ Max SequenceNumberAction = "max"
+ Update SequenceNumberAction = "update"
+)
+
+// SetPropertiesPreparer prepares the SetProperties request.
+func (client Client) SetPropertiesPreparer(ctx context.Context, accountName, containerName, blobName string, input SetPropertiesInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "properties"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.CacheControl != nil {
+ headers["x-ms-blob-cache-control"] = *input.CacheControl
+ }
+ if input.ContentDisposition != nil {
+ headers["x-ms-blob-content-disposition"] = *input.ContentDisposition
+ }
+ if input.ContentEncoding != nil {
+ headers["x-ms-blob-content-encoding"] = *input.ContentEncoding
+ }
+ if input.ContentLanguage != nil {
+ headers["x-ms-blob-content-language"] = *input.ContentLanguage
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.ContentType != nil {
+ headers["x-ms-blob-content-type"] = *input.ContentType
+ }
+ if input.ContentLength != nil {
+ headers["x-ms-blob-content-length"] = *input.ContentLength
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+ if input.SequenceNumberAction != nil {
+ headers["x-ms-sequence-number-action"] = string(*input.SequenceNumberAction)
+ }
+ if input.BlobSequenceNumber != nil {
+ headers["x-ms-blob-sequence-number"] = *input.BlobSequenceNumber
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetPropertiesSender sends the SetProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetPropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetPropertiesResponder handles the response to the SetProperties request. The method always
+// closes the http.Response Body.
+func (client Client) SetPropertiesResponder(resp *http.Response) (result SetPropertiesResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.BlobSequenceNumber = resp.Header.Get("x-ms-blob-sequence-number")
+ result.Etag = resp.Header.Get("Etag")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/put_append_blob.go b/storage/2018-03-28/blob/blobs/put_append_blob.go
new file mode 100644
index 0000000..ef2c502
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/put_append_blob.go
@@ -0,0 +1,134 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type PutAppendBlobInput struct {
+ CacheControl *string
+ ContentDisposition *string
+ ContentEncoding *string
+ ContentLanguage *string
+ ContentMD5 *string
+ ContentType *string
+ LeaseID *string
+ MetaData map[string]string
+}
+
+// PutAppendBlob is a wrapper around the Put API call (with a stricter input object)
+// which creates a new append blob, or updates the content of an existing blob.
+func (client Client) PutAppendBlob(ctx context.Context, accountName, containerName, blobName string, input PutAppendBlobInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutAppendBlob", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutAppendBlob", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutAppendBlob", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutAppendBlob", "`blobName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("blobs.Client", "PutAppendBlob", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.PutAppendBlobPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutAppendBlob", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutAppendBlobSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutAppendBlob", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutAppendBlobResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutAppendBlob", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutAppendBlobPreparer prepares the PutAppendBlob request.
+func (client Client) PutAppendBlobPreparer(ctx context.Context, accountName, containerName, blobName string, input PutAppendBlobInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-blob-type": string(AppendBlob),
+ "x-ms-version": APIVersion,
+
+ // For a page blob or an append blob, the value of this header must be set to zero,
+ // as Put Blob is used only to initialize the blob
+ "Content-Length": 0,
+ }
+
+ if input.CacheControl != nil {
+ headers["x-ms-blob-cache-control"] = *input.CacheControl
+ }
+ if input.ContentDisposition != nil {
+ headers["x-ms-blob-content-disposition"] = *input.ContentDisposition
+ }
+ if input.ContentEncoding != nil {
+ headers["x-ms-blob-content-encoding"] = *input.ContentEncoding
+ }
+ if input.ContentLanguage != nil {
+ headers["x-ms-blob-content-language"] = *input.ContentLanguage
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.ContentType != nil {
+ headers["x-ms-blob-content-type"] = *input.ContentType
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutAppendBlobSender sends the PutAppendBlob request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutAppendBlobSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutAppendBlobResponder handles the response to the PutAppendBlob request. The method always
+// closes the http.Response Body.
+func (client Client) PutAppendBlobResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/put_block.go b/storage/2018-03-28/blob/blobs/put_block.go
new file mode 100644
index 0000000..5256013
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/put_block.go
@@ -0,0 +1,125 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type PutBlockInput struct {
+ BlockID string
+ Content []byte
+ ContentMD5 *string
+ LeaseID *string
+}
+
+type PutBlockResult struct {
+ autorest.Response
+
+ ContentMD5 string
+}
+
+// PutBlock creates a new block to be committed as part of a blob.
+func (client Client) PutBlock(ctx context.Context, accountName, containerName, blobName string, input PutBlockInput) (result PutBlockResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlock", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlock", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutBlock", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlock", "`blobName` cannot be an empty string.")
+ }
+ if input.BlockID == "" {
+ return result, validation.NewError("blobs.Client", "PutBlock", "`input.BlockID` cannot be an empty string.")
+ }
+ if len(input.Content) == 0 {
+ return result, validation.NewError("blobs.Client", "PutBlock", "`input.Content` cannot be empty.")
+ }
+
+ req, err := client.PutBlockPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlock", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutBlockSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlock", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutBlockResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlock", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutBlockPreparer prepares the PutBlock request.
+func (client Client) PutBlockPreparer(ctx context.Context, accountName, containerName, blobName string, input PutBlockInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "block"),
+ "blockid": autorest.Encode("query", input.BlockID),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithBytes(&input.Content))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutBlockSender sends the PutBlock request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutBlockSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutBlockResponder handles the response to the PutBlock request. The method always
+// closes the http.Response Body.
+func (client Client) PutBlockResponder(resp *http.Response) (result PutBlockResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.ContentMD5 = resp.Header.Get("Content-MD5")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/put_block_blob.go b/storage/2018-03-28/blob/blobs/put_block_blob.go
new file mode 100644
index 0000000..fa29dd3
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/put_block_blob.go
@@ -0,0 +1,135 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type PutBlockBlobInput struct {
+ CacheControl *string
+ Content []byte
+ ContentDisposition *string
+ ContentEncoding *string
+ ContentLanguage *string
+ ContentMD5 *string
+ ContentType *string
+ LeaseID *string
+ MetaData map[string]string
+}
+
+// PutBlockBlob is a wrapper around the Put API call (with a stricter input object)
+// which creates a new block append blob, or updates the content of an existing block blob.
+func (client Client) PutBlockBlob(ctx context.Context, accountName, containerName, blobName string, input PutBlockBlobInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockBlob", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockBlob", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutBlockBlob", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockBlob", "`blobName` cannot be an empty string.")
+ }
+ if len(input.Content) == 0 {
+ return result, validation.NewError("blobs.Client", "PutBlockBlob", "`input.Content` cannot be empty.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("blobs.Client", "PutBlockBlob", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.PutBlockBlobPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockBlob", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutBlockBlobSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockBlob", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutBlockBlobResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockBlob", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutBlockBlobPreparer prepares the PutBlockBlob request.
+func (client Client) PutBlockBlobPreparer(ctx context.Context, accountName, containerName, blobName string, input PutBlockBlobInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-blob-type": string(BlockBlob),
+ "x-ms-version": APIVersion,
+ }
+
+ if input.CacheControl != nil {
+ headers["x-ms-blob-cache-control"] = *input.CacheControl
+ }
+ if input.ContentDisposition != nil {
+ headers["x-ms-blob-content-disposition"] = *input.ContentDisposition
+ }
+ if input.ContentEncoding != nil {
+ headers["x-ms-blob-content-encoding"] = *input.ContentEncoding
+ }
+ if input.ContentLanguage != nil {
+ headers["x-ms-blob-content-language"] = *input.ContentLanguage
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.ContentType != nil {
+ headers["x-ms-blob-content-type"] = *input.ContentType
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithBytes(&input.Content))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutBlockBlobSender sends the PutBlockBlob request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutBlockBlobSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutBlockBlobResponder handles the response to the PutBlockBlob request. The method always
+// closes the http.Response Body.
+func (client Client) PutBlockBlobResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/put_block_blob_file.go b/storage/2018-03-28/blob/blobs/put_block_blob_file.go
new file mode 100644
index 0000000..7232e5e
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/put_block_blob_file.go
@@ -0,0 +1,34 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "os"
+)
+
+// PutBlockBlobFromFile is a helper method which takes a file, and automatically chunks it up, rather than having to do this yourself
+func (client Client) PutBlockBlobFromFile(ctx context.Context, accountName, containerName, blobName string, file *os.File, input PutBlockBlobInput) error {
+ fileInfo, err := file.Stat()
+ if err != nil {
+ return fmt.Errorf("Error loading file info: %s", err)
+ }
+
+ fileSize := fileInfo.Size()
+ bytes := make([]byte, fileSize)
+
+ _, err = file.ReadAt(bytes, 0)
+ if err != nil {
+ if err != io.EOF {
+ return fmt.Errorf("Error reading bytes: %s", err)
+ }
+ }
+
+ input.Content = bytes
+
+ if _, err = client.PutBlockBlob(ctx, accountName, containerName, blobName, input); err != nil {
+ return fmt.Errorf("Error putting bytes: %s", err)
+ }
+
+ return nil
+}
diff --git a/storage/2018-03-28/blob/blobs/put_block_list.go b/storage/2018-03-28/blob/blobs/put_block_list.go
new file mode 100644
index 0000000..f805247
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/put_block_list.go
@@ -0,0 +1,157 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type BlockList struct {
+ CommittedBlockIDs []BlockID `xml:"Committed,omitempty"`
+ UncommittedBlockIDs []BlockID `xml:"Uncommitted,omitempty"`
+ LatestBlockIDs []BlockID `xml:"Latest,omitempty"`
+}
+
+type BlockID struct {
+ Value string `xml:",chardata"`
+}
+
+type PutBlockListInput struct {
+ BlockList BlockList
+ CacheControl *string
+ ContentDisposition *string
+ ContentEncoding *string
+ ContentLanguage *string
+ ContentMD5 *string
+ ContentType *string
+ MetaData map[string]string
+ LeaseID *string
+}
+
+type PutBlockListResult struct {
+ autorest.Response
+
+ ContentMD5 string
+ ETag string
+ LastModified string
+}
+
+// PutBlockList writes a blob by specifying the list of block IDs that make up the blob.
+// In order to be written as part of a blob, a block must have been successfully written
+// to the server in a prior Put Block operation.
+func (client Client) PutBlockList(ctx context.Context, accountName, containerName, blobName string, input PutBlockListInput) (result PutBlockListResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockList", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockList", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutBlockList", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockList", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.PutBlockListPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockList", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutBlockListSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockList", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutBlockListResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockList", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutBlockListPreparer prepares the PutBlockList request.
+func (client Client) PutBlockListPreparer(ctx context.Context, accountName, containerName, blobName string, input PutBlockListInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "blocklist"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.CacheControl != nil {
+ headers["x-ms-blob-cache-control"] = *input.CacheControl
+ }
+ if input.ContentDisposition != nil {
+ headers["x-ms-blob-content-disposition"] = *input.ContentDisposition
+ }
+ if input.ContentEncoding != nil {
+ headers["x-ms-blob-content-encoding"] = *input.ContentEncoding
+ }
+ if input.ContentLanguage != nil {
+ headers["x-ms-blob-content-language"] = *input.ContentLanguage
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.ContentType != nil {
+ headers["x-ms-blob-content-type"] = *input.ContentType
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithXML(input.BlockList))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutBlockListSender sends the PutBlockList request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutBlockListSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutBlockListResponder handles the response to the PutBlockList request. The method always
+// closes the http.Response Body.
+func (client Client) PutBlockListResponder(resp *http.Response) (result PutBlockListResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.ContentMD5 = resp.Header.Get("Content-MD5")
+ result.ETag = resp.Header.Get("ETag")
+ result.LastModified = resp.Header.Get("Last-Modified")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/put_block_url.go b/storage/2018-03-28/blob/blobs/put_block_url.go
new file mode 100644
index 0000000..95ad974
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/put_block_url.go
@@ -0,0 +1,129 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type PutBlockFromURLInput struct {
+ BlockID string
+ CopySource string
+
+ ContentMD5 *string
+ LeaseID *string
+ Range *string
+}
+
+type PutBlockFromURLResult struct {
+ autorest.Response
+ ContentMD5 string
+}
+
+// PutBlockFromURL creates a new block to be committed as part of a blob where the contents are read from a URL
+func (client Client) PutBlockFromURL(ctx context.Context, accountName, containerName, blobName string, input PutBlockFromURLInput) (result PutBlockFromURLResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockFromURL", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockFromURL", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutBlockFromURL", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockFromURL", "`blobName` cannot be an empty string.")
+ }
+ if input.BlockID == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockFromURL", "`input.BlockID` cannot be an empty string.")
+ }
+ if input.CopySource == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockFromURL", "`input.CopySource` cannot be an empty string.")
+ }
+
+ req, err := client.PutBlockFromURLPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockFromURL", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutBlockFromURLSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockFromURL", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutBlockFromURLResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockFromURL", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutBlockFromURLPreparer prepares the PutBlockFromURL request.
+func (client Client) PutBlockFromURLPreparer(ctx context.Context, accountName, containerName, blobName string, input PutBlockFromURLInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "block"),
+ "blockid": autorest.Encode("query", input.BlockID),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-copy-source": input.CopySource,
+ }
+
+ if input.ContentMD5 != nil {
+ headers["x-ms-source-content-md5"] = *input.ContentMD5
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+ if input.Range != nil {
+ headers["x-ms-source-range"] = *input.Range
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutBlockFromURLSender sends the PutBlockFromURL request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutBlockFromURLSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutBlockFromURLResponder handles the response to the PutBlockFromURL request. The method always
+// closes the http.Response Body.
+func (client Client) PutBlockFromURLResponder(resp *http.Response) (result PutBlockFromURLResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.ContentMD5 = resp.Header.Get("Content-MD5")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/put_page_blob.go b/storage/2018-03-28/blob/blobs/put_page_blob.go
new file mode 100644
index 0000000..ad3c878
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/put_page_blob.go
@@ -0,0 +1,148 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type PutPageBlobInput struct {
+ CacheControl *string
+ ContentDisposition *string
+ ContentEncoding *string
+ ContentLanguage *string
+ ContentMD5 *string
+ ContentType *string
+ LeaseID *string
+ MetaData map[string]string
+
+ BlobContentLengthBytes int64
+ BlobSequenceNumber *int64
+ AccessTier *AccessTier
+}
+
+// PutPageBlob is a wrapper around the Put API call (with a stricter input object)
+// which creates a new block blob, or updates the content of an existing page blob.
+func (client Client) PutPageBlob(ctx context.Context, accountName, containerName, blobName string, input PutPageBlobInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageBlob", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageBlob", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutPageBlob", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageBlob", "`blobName` cannot be an empty string.")
+ }
+ if input.BlobContentLengthBytes == 0 || input.BlobContentLengthBytes%512 != 0 {
+ return result, validation.NewError("blobs.Client", "PutPageBlob", "`blobName` must be aligned to a 512-byte boundary.")
+ }
+
+ req, err := client.PutPageBlobPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageBlob", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutPageBlobSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageBlob", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutPageBlobResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageBlob", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutPageBlobPreparer prepares the PutPageBlob request.
+func (client Client) PutPageBlobPreparer(ctx context.Context, accountName, containerName, blobName string, input PutPageBlobInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-blob-type": string(PageBlob),
+ "x-ms-version": APIVersion,
+
+ // For a page blob or an page blob, the value of this header must be set to zero,
+ // as Put Blob is used only to initialize the blob
+ "Content-Length": 0,
+
+ // This header specifies the maximum size for the page blob, up to 8 TB.
+ // The page blob size must be aligned to a 512-byte boundary.
+ "x-ms-blob-content-length": input.BlobContentLengthBytes,
+ }
+
+ if input.AccessTier != nil {
+ headers["x-ms-access-tier"] = string(*input.AccessTier)
+ }
+ if input.BlobSequenceNumber != nil {
+ headers["x-ms-blob-sequence-number"] = *input.BlobSequenceNumber
+ }
+
+ if input.CacheControl != nil {
+ headers["x-ms-blob-cache-control"] = *input.CacheControl
+ }
+ if input.ContentDisposition != nil {
+ headers["x-ms-blob-content-disposition"] = *input.ContentDisposition
+ }
+ if input.ContentEncoding != nil {
+ headers["x-ms-blob-content-encoding"] = *input.ContentEncoding
+ }
+ if input.ContentLanguage != nil {
+ headers["x-ms-blob-content-language"] = *input.ContentLanguage
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.ContentType != nil {
+ headers["x-ms-blob-content-type"] = *input.ContentType
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutPageBlobSender sends the PutPageBlob request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutPageBlobSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutPageBlobResponder handles the response to the PutPageBlob request. The method always
+// closes the http.Response Body.
+func (client Client) PutPageBlobResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/put_page_clear.go b/storage/2018-03-28/blob/blobs/put_page_clear.go
new file mode 100644
index 0000000..59feaa5
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/put_page_clear.go
@@ -0,0 +1,113 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type PutPageClearInput struct {
+ StartByte int64
+ EndByte int64
+
+ LeaseID *string
+}
+
+// PutPageClear clears a range of pages within a page blob.
+func (client Client) PutPageClear(ctx context.Context, accountName, containerName, blobName string, input PutPageClearInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageClear", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageClear", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutPageClear", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageClear", "`blobName` cannot be an empty string.")
+ }
+ if input.StartByte < 0 {
+ return result, validation.NewError("blobs.Client", "PutPageClear", "`input.StartByte` must be greater than or equal to 0.")
+ }
+ if input.EndByte <= 0 {
+ return result, validation.NewError("blobs.Client", "PutPageClear", "`input.EndByte` must be greater than 0.")
+ }
+
+ req, err := client.PutPageClearPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageClear", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutPageClearSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageClear", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutPageClearResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageClear", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutPageClearPreparer prepares the PutPageClear request.
+func (client Client) PutPageClearPreparer(ctx context.Context, accountName, containerName, blobName string, input PutPageClearInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "page"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-page-write": "clear",
+ "x-ms-range": fmt.Sprintf("bytes=%d-%d", input.StartByte, input.EndByte),
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutPageClearSender sends the PutPageClear request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutPageClearSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutPageClearResponder handles the response to the PutPageClear request. The method always
+// closes the http.Response Body.
+func (client Client) PutPageClearResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/put_page_update.go b/storage/2018-03-28/blob/blobs/put_page_update.go
new file mode 100644
index 0000000..a47e8ca
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/put_page_update.go
@@ -0,0 +1,163 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type PutPageUpdateInput struct {
+ StartByte int64
+ EndByte int64
+ Content []byte
+
+ IfSequenceNumberEQ *string
+ IfSequenceNumberLE *string
+ IfSequenceNumberLT *string
+ IfModifiedSince *string
+ IfUnmodifiedSince *string
+ IfMatch *string
+ IfNoneMatch *string
+ LeaseID *string
+}
+
+type PutPageUpdateResult struct {
+ autorest.Response
+
+ BlobSequenceNumber string
+ ContentMD5 string
+ LastModified string
+}
+
+// PutPageUpdate writes a range of pages to a page blob.
+func (client Client) PutPageUpdate(ctx context.Context, accountName, containerName, blobName string, input PutPageUpdateInput) (result PutPageUpdateResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", "`blobName` cannot be an empty string.")
+ }
+ if input.StartByte < 0 {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", "`input.StartByte` must be greater than or equal to 0.")
+ }
+ if input.EndByte <= 0 {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", "`input.EndByte` must be greater than 0.")
+ }
+
+ expectedSize := (input.EndByte - input.StartByte) + 1
+ actualSize := int64(len(input.Content))
+ if expectedSize != actualSize {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", fmt.Sprintf("Content Size was defined as %d but got %d.", expectedSize, actualSize))
+ }
+
+ req, err := client.PutPageUpdatePreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageUpdate", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutPageUpdateSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageUpdate", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutPageUpdateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageUpdate", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutPageUpdatePreparer prepares the PutPageUpdate request.
+func (client Client) PutPageUpdatePreparer(ctx context.Context, accountName, containerName, blobName string, input PutPageUpdateInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "page"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-page-write": "update",
+ "x-ms-range": fmt.Sprintf("bytes=%d-%d", input.StartByte, input.EndByte),
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+ if input.IfSequenceNumberEQ != nil {
+ headers["x-ms-if-sequence-number-eq"] = *input.IfSequenceNumberEQ
+ }
+ if input.IfSequenceNumberLE != nil {
+ headers["x-ms-if-sequence-number-le"] = *input.IfSequenceNumberLE
+ }
+ if input.IfSequenceNumberLT != nil {
+ headers["x-ms-if-sequence-number-lt"] = *input.IfSequenceNumberLT
+ }
+ if input.IfModifiedSince != nil {
+ headers["If-Modified-Since"] = *input.IfModifiedSince
+ }
+ if input.IfUnmodifiedSince != nil {
+ headers["If-Unmodified-Since"] = *input.IfUnmodifiedSince
+ }
+ if input.IfMatch != nil {
+ headers["If-Match"] = *input.IfMatch
+ }
+ if input.IfNoneMatch != nil {
+ headers["If-None-Match"] = *input.IfNoneMatch
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithBytes(&input.Content))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutPageUpdateSender sends the PutPageUpdate request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutPageUpdateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutPageUpdateResponder handles the response to the PutPageUpdate request. The method always
+// closes the http.Response Body.
+func (client Client) PutPageUpdateResponder(resp *http.Response) (result PutPageUpdateResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.BlobSequenceNumber = resp.Header.Get("x-ms-blob-sequence-number")
+ result.ContentMD5 = resp.Header.Get("Content-MD5")
+ result.LastModified = resp.Header.Get("Last-Modified")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/resource_id.go b/storage/2018-03-28/blob/blobs/resource_id.go
new file mode 100644
index 0000000..0f6dddf
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/resource_id.go
@@ -0,0 +1,56 @@
+package blobs
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Blob
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, containerName, blobName string) string {
+ domain := endpoints.GetBlobEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s/%s", domain, containerName, blobName)
+}
+
+type ResourceID struct {
+ AccountName string
+ ContainerName string
+ BlobName string
+}
+
+// ParseResourceID parses the Resource ID and returns an object which can be used
+// to interact with the Blob Resource
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://foo.blob.core.windows.net/Bar/example.vhd
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ path := strings.TrimPrefix(uri.Path, "/")
+ segments := strings.Split(path, "/")
+ if len(segments) == 0 {
+ return nil, fmt.Errorf("Expected the path to contain segments but got none")
+ }
+
+ containerName := segments[0]
+ blobName := strings.TrimPrefix(path, containerName)
+ blobName = strings.TrimPrefix(blobName, "/")
+ return &ResourceID{
+ AccountName: *accountName,
+ ContainerName: containerName,
+ BlobName: blobName,
+ }, nil
+}
diff --git a/storage/2018-03-28/blob/blobs/resource_id_test.go b/storage/2018-03-28/blob/blobs/resource_id_test.go
new file mode 100644
index 0000000..bb6cad1
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/resource_id_test.go
@@ -0,0 +1,123 @@
+package blobs
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.blob.core.chinacloudapi.cn/container1/blob1.vhd",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.blob.core.cloudapi.de/container1/blob1.vhd",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.blob.core.windows.net/container1/blob1.vhd",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.blob.core.usgovcloudapi.net/container1/blob1.vhd",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "container1", "blob1.vhd")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.blob.core.chinacloudapi.cn/container1/blob1.vhd",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.blob.core.cloudapi.de/container1/blob1.vhd",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.blob.core.windows.net/container1/blob1.vhd",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.blob.core.usgovcloudapi.net/container1/blob1.vhd",
+ },
+ }
+ t.Logf("[DEBUG] Top Level Files")
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.ContainerName != "container1" {
+ t.Fatalf("Expected Container Name to be `container1` but got %q", actual.ContainerName)
+ }
+ if actual.BlobName != "blob1.vhd" {
+ t.Fatalf("Expected Blob Name to be `blob1.vhd` but got %q", actual.BlobName)
+ }
+ }
+
+ testData = []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.blob.core.chinacloudapi.cn/container1/example/blob1.vhd",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.blob.core.cloudapi.de/container1/example/blob1.vhd",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.blob.core.windows.net/container1/example/blob1.vhd",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.blob.core.usgovcloudapi.net/container1/example/blob1.vhd",
+ },
+ }
+ t.Logf("[DEBUG] Nested Files")
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.ContainerName != "container1" {
+ t.Fatalf("Expected Container Name to be `container1` but got %q", actual.ContainerName)
+ }
+ if actual.BlobName != "example/blob1.vhd" {
+ t.Fatalf("Expected Blob Name to be `example/blob1.vhd` but got %q", actual.BlobName)
+ }
+ }
+}
diff --git a/storage/2018-03-28/blob/blobs/set_tier.go b/storage/2018-03-28/blob/blobs/set_tier.go
new file mode 100644
index 0000000..dd0f0b8
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/set_tier.go
@@ -0,0 +1,93 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// SetTier sets the tier on a blob.
+func (client Client) SetTier(ctx context.Context, accountName, containerName, blobName string, tier AccessTier) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "SetTier", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "SetTier", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "SetTier", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "SetTier", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.SetTierPreparer(ctx, accountName, containerName, blobName, tier)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetTier", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetTierSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetTier", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetTierResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetTier", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetTierPreparer prepares the SetTier request.
+func (client Client) SetTierPreparer(ctx context.Context, accountName, containerName, blobName string, tier AccessTier) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "tier"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-access-tier": string(tier),
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetTierSender sends the SetTier request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetTierSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetTierResponder handles the response to the SetTier request. The method always
+// closes the http.Response Body.
+func (client Client) SetTierResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/snapshot.go b/storage/2018-03-28/blob/blobs/snapshot.go
new file mode 100644
index 0000000..180070b
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/snapshot.go
@@ -0,0 +1,163 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type SnapshotInput struct {
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+
+ // MetaData is a user-defined name-value pair associated with the blob.
+ // If no name-value pairs are specified, the operation will copy the base blob metadata to the snapshot.
+ // If one or more name-value pairs are specified, the snapshot is created with the specified metadata,
+ // and metadata is not copied from the base blob.
+ MetaData map[string]string
+
+ // A DateTime value which will only snapshot the blob if it has been modified since the specified date/time
+ // If the base blob has not been modified, the Blob service returns status code 412 (Precondition Failed).
+ IfModifiedSince *string
+
+ // A DateTime value which will only snapshot the blob if it has not been modified since the specified date/time
+ // If the base blob has been modified, the Blob service returns status code 412 (Precondition Failed).
+ IfUnmodifiedSince *string
+
+ // An ETag value to snapshot the blob only if its ETag value matches the value specified.
+ // If the values do not match, the Blob service returns status code 412 (Precondition Failed).
+ IfMatch *string
+
+ // An ETag value for this conditional header to snapshot the blob only if its ETag value
+ // does not match the value specified.
+ // If the values are identical, the Blob service returns status code 412 (Precondition Failed).
+ IfNoneMatch *string
+}
+
+type SnapshotResult struct {
+ autorest.Response
+
+ // The ETag of the snapshot
+ ETag string
+
+ // A DateTime value that uniquely identifies the snapshot.
+ // The value of this header indicates the snapshot version,
+ // and may be used in subsequent requests to access the snapshot.
+ SnapshotDateTime string
+}
+
+// Snapshot captures a Snapshot of a given Blob
+func (client Client) Snapshot(ctx context.Context, accountName, containerName, blobName string, input SnapshotInput) (result SnapshotResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "Snapshot", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "Snapshot", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "Snapshot", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "Snapshot", "`blobName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("blobs.Client", "Snapshot", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.SnapshotPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Snapshot", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SnapshotSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Snapshot", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SnapshotResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Snapshot", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SnapshotPreparer prepares the Snapshot request.
+func (client Client) SnapshotPreparer(ctx context.Context, accountName, containerName, blobName string, input SnapshotInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "snapshot"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ if input.IfModifiedSince != nil {
+ headers["If-Modified-Since"] = *input.IfModifiedSince
+ }
+ if input.IfUnmodifiedSince != nil {
+ headers["If-Unmodified-Since"] = *input.IfUnmodifiedSince
+ }
+ if input.IfMatch != nil {
+ headers["If-Match"] = *input.IfMatch
+ }
+ if input.IfNoneMatch != nil {
+ headers["If-None-Match"] = *input.IfNoneMatch
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SnapshotSender sends the Snapshot request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SnapshotSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SnapshotResponder handles the response to the Snapshot request. The method always
+// closes the http.Response Body.
+func (client Client) SnapshotResponder(resp *http.Response) (result SnapshotResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.ETag = resp.Header.Get("ETag")
+ result.SnapshotDateTime = resp.Header.Get("x-ms-snapshot")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/snapshot_get_properties.go b/storage/2018-03-28/blob/blobs/snapshot_get_properties.go
new file mode 100644
index 0000000..fe1be63
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/snapshot_get_properties.go
@@ -0,0 +1,90 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetSnapshotPropertiesInput struct {
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+
+ // The ID of the Snapshot which should be retrieved
+ SnapshotID string
+}
+
+// GetSnapshotProperties returns all user-defined metadata, standard HTTP properties, and system properties for
+// the specified snapshot of a blob
+func (client Client) GetSnapshotProperties(ctx context.Context, accountName, containerName, blobName string, input GetSnapshotPropertiesInput) (result GetPropertiesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "GetSnapshotProperties", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "GetSnapshotProperties", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "GetSnapshotProperties", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "GetSnapshotProperties", "`blobName` cannot be an empty string.")
+ }
+ if input.SnapshotID == "" {
+ return result, validation.NewError("blobs.Client", "GetSnapshotProperties", "`input.SnapshotID` cannot be an empty string.")
+ }
+
+ req, err := client.GetSnapshotPropertiesPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetSnapshotProperties", nil, "Failure preparing request")
+ return
+ }
+
+ // we re-use the GetProperties methods since this is otherwise the same
+ resp, err := client.GetPropertiesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetSnapshotProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetSnapshotProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetSnapshotPreparer prepares the GetSnapshot request.
+func (client Client) GetSnapshotPropertiesPreparer(ctx context.Context, accountName, containerName, blobName string, input GetSnapshotPropertiesInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "snapshot": autorest.Encode("query", input.SnapshotID),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsHead(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
diff --git a/storage/2018-03-28/blob/blobs/snapshot_test.go b/storage/2018-03-28/blob/blobs/snapshot_test.go
new file mode 100644
index 0000000..c8d7585
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/snapshot_test.go
@@ -0,0 +1,159 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "testing"
+ "time"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/blob/containers"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestSnapshotLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "example.txt"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithStorageResourceManagerAuth(containersClient.Client)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatalf("Error creating: %s", err)
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Copying file to Blob Storage..")
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+
+ refreshInterval := 5 * time.Second
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, fileName, copyInput, refreshInterval); err != nil {
+ t.Fatalf("Error copying: %s", err)
+ }
+
+ t.Logf("[DEBUG] First Snapshot..")
+ firstSnapshot, err := blobClient.Snapshot(ctx, accountName, containerName, fileName, SnapshotInput{})
+ if err != nil {
+ t.Fatalf("Error taking first snapshot: %s", err)
+ }
+ t.Logf("[DEBUG] First Snapshot ID: %q", firstSnapshot.SnapshotDateTime)
+
+ t.Log("[DEBUG] Waiting 2 seconds..")
+ time.Sleep(2 * time.Second)
+
+ t.Logf("[DEBUG] Second Snapshot..")
+ secondSnapshot, err := blobClient.Snapshot(ctx, accountName, containerName, fileName, SnapshotInput{
+ MetaData: map[string]string{
+ "hello": "world",
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error taking Second snapshot: %s", err)
+ }
+ t.Logf("[DEBUG] Second Snapshot ID: %q", secondSnapshot.SnapshotDateTime)
+
+ t.Logf("[DEBUG] Leasing the Blob..")
+ leaseDetails, err := blobClient.AcquireLease(ctx, accountName, containerName, fileName, AcquireLeaseInput{
+ // infinite
+ LeaseDuration: -1,
+ })
+ if err != nil {
+ t.Fatalf("Error leasing Blob: %s", err)
+ }
+ t.Logf("[DEBUG] Lease ID: %q", leaseDetails.LeaseID)
+
+ t.Logf("[DEBUG] Third Snapshot..")
+ thirdSnapshot, err := blobClient.Snapshot(ctx, accountName, containerName, fileName, SnapshotInput{
+ LeaseID: &leaseDetails.LeaseID,
+ })
+ if err != nil {
+ t.Fatalf("Error taking Third snapshot: %s", err)
+ }
+ t.Logf("[DEBUG] Third Snapshot ID: %q", thirdSnapshot.SnapshotDateTime)
+
+ t.Logf("[DEBUG] Releasing Lease..")
+ if _, err := blobClient.ReleaseLease(ctx, accountName, containerName, fileName, leaseDetails.LeaseID); err != nil {
+ t.Fatalf("Error releasing Lease: %s", err)
+ }
+
+ // get the properties from the blob, which should include the LastModifiedDate
+ t.Logf("[DEBUG] Retrieving Properties for Blob")
+ props, err := blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error getting properties: %s", err)
+ }
+
+ // confirm that the If-Modified-None returns an error
+ t.Logf("[DEBUG] Third Snapshot..")
+ fourthSnapshot, err := blobClient.Snapshot(ctx, accountName, containerName, fileName, SnapshotInput{
+ LeaseID: &leaseDetails.LeaseID,
+ IfModifiedSince: &props.LastModified,
+ })
+ if err == nil {
+ t.Fatalf("Expected an error but didn't get one")
+ }
+ if fourthSnapshot.Response.StatusCode != http.StatusPreconditionFailed {
+ t.Fatalf("Expected the status code to be Precondition Failed but got: %d", fourthSnapshot.Response.StatusCode)
+ }
+
+ t.Logf("[DEBUG] Retrieving the Second Snapshot Properties..")
+ getSecondSnapshotInput := GetSnapshotPropertiesInput{
+ SnapshotID: secondSnapshot.SnapshotDateTime,
+ }
+ if _, err := blobClient.GetSnapshotProperties(ctx, accountName, containerName, fileName, getSecondSnapshotInput); err != nil {
+ t.Fatalf("Error retrieving properties for the second snapshot: %s", err)
+ }
+
+ t.Logf("[DEBUG] Deleting the Second Snapshot..")
+ deleteSnapshotInput := DeleteSnapshotInput{
+ SnapshotDateTime: secondSnapshot.SnapshotDateTime,
+ }
+ if _, err := blobClient.DeleteSnapshot(ctx, accountName, containerName, fileName, deleteSnapshotInput); err != nil {
+ t.Fatalf("Error deleting snapshot: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-Retrieving the Second Snapshot Properties..")
+ secondSnapshotProps, err := blobClient.GetSnapshotProperties(ctx, accountName, containerName, fileName, getSecondSnapshotInput)
+ if err == nil {
+ t.Fatalf("Expected an error retrieving the snapshot but got none")
+ }
+ if secondSnapshotProps.Response.StatusCode != http.StatusNotFound {
+ t.Fatalf("Expected the status code to be %d but got %q", http.StatusNoContent, secondSnapshotProps.Response.StatusCode)
+ }
+
+ t.Logf("[DEBUG] Deleting all the snapshots..")
+ if _, err := blobClient.DeleteSnapshots(ctx, accountName, containerName, fileName, DeleteSnapshotsInput{}); err != nil {
+ t.Fatalf("Error deleting snapshots: %s", err)
+ }
+
+ t.Logf("[DEBUG] Deleting the Blob..")
+ deleteInput := DeleteInput{
+ DeleteSnapshots: false,
+ }
+ if _, err := blobClient.Delete(ctx, accountName, containerName, fileName, deleteInput); err != nil {
+ t.Fatalf("Error deleting Blob: %s", err)
+ }
+}
diff --git a/storage/2018-03-28/blob/blobs/undelete.go b/storage/2018-03-28/blob/blobs/undelete.go
new file mode 100644
index 0000000..9be2f81
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/undelete.go
@@ -0,0 +1,92 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Undelete restores the contents and metadata of soft deleted blob and any associated soft deleted snapshots.
+func (client Client) Undelete(ctx context.Context, accountName, containerName, blobName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "Undelete", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "Undelete", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "Undelete", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "Undelete", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.UndeletePreparer(ctx, accountName, containerName, blobName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Undelete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.UndeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Undelete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.UndeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Undelete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// UndeletePreparer prepares the Undelete request.
+func (client Client) UndeletePreparer(ctx context.Context, accountName, containerName, blobName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "undelete"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// UndeleteSender sends the Undelete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) UndeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// UndeleteResponder handles the response to the Undelete request. The method always
+// closes the http.Response Body.
+func (client Client) UndeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-03-28/blob/blobs/version.go b/storage/2018-03-28/blob/blobs/version.go
new file mode 100644
index 0000000..b1e5fa9
--- /dev/null
+++ b/storage/2018-03-28/blob/blobs/version.go
@@ -0,0 +1,14 @@
+package blobs
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2018-03-28"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2018-03-28/blob/containers/README.md b/storage/2018-03-28/blob/containers/README.md
new file mode 100644
index 0000000..b6e4c8e
--- /dev/null
+++ b/storage/2018-03-28/blob/containers/README.md
@@ -0,0 +1,45 @@
+## Blob Storage Container SDK for API version 2018-03-28
+
+This package allows you to interact with the Containers Blob Storage API
+
+### Supported Authorizers
+
+* Azure Active Directory (for the Resource Endpoint `https://storage.azure.com`)
+* SharedKeyLite (Blob, File & Queue)
+
+Note: when using the `ListBlobs` operation, only `SharedKeyLite` authentication is supported.
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/blob/containers"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ containerName := "mycontainer"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ containersClient := containers.New()
+ containersClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ createInput := containers.CreateInput{
+ AccessLevel: containers.Private,
+ }
+ if _, err := containersClient.Create(ctx, accountName, containerName, createInput); err != nil {
+ return fmt.Errorf("Error creating Container: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2018-03-28/blob/containers/client.go b/storage/2018-03-28/blob/containers/client.go
new file mode 100644
index 0000000..7bf4947
--- /dev/null
+++ b/storage/2018-03-28/blob/containers/client.go
@@ -0,0 +1,34 @@
+package containers
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for Blob Storage Containers.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithBaseURI creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
+
+func (client Client) setAccessLevelIntoHeaders(headers map[string]interface{}, level AccessLevel) map[string]interface{} {
+ // If this header is not included in the request, container data is private to the account owner.
+ if level != Private {
+ headers["x-ms-blob-public-access"] = string(level)
+ }
+
+ return headers
+}
diff --git a/storage/2018-03-28/blob/containers/create.go b/storage/2018-03-28/blob/containers/create.go
new file mode 100644
index 0000000..84c2887
--- /dev/null
+++ b/storage/2018-03-28/blob/containers/create.go
@@ -0,0 +1,123 @@
+package containers
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type CreateInput struct {
+ // Specifies whether data in the container may be accessed publicly and the level of access
+ AccessLevel AccessLevel
+
+ // A name-value pair to associate with the container as metadata.
+ MetaData map[string]string
+}
+
+type CreateResponse struct {
+ autorest.Response
+ Error *ErrorResponse `xml:"Error"`
+}
+
+// Create creates a new container under the specified account.
+// If the container with the same name already exists, the operation fails.
+func (client Client) Create(ctx context.Context, accountName, containerName string, input CreateInput) (result CreateResponse, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "Create", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "Create", "`containerName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("containers.Client", "Create", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.CreatePreparer(ctx, accountName, containerName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "Create", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "Create", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "Create", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreatePreparer prepares the Create request.
+func (client Client) CreatePreparer(ctx context.Context, accountName string, containerName string, input CreateInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = client.setAccessLevelIntoHeaders(headers, input.AccessLevel)
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSender sends the Create request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateResponder handles the response to the Create request. The method always
+// closes the http.Response Body.
+func (client Client) CreateResponder(resp *http.Response) (result CreateResponse, err error) {
+ successfulStatusCodes := []int{
+ http.StatusCreated,
+ }
+ if autorest.ResponseHasStatusCode(resp, successfulStatusCodes...) {
+ // when successful there's no response
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(successfulStatusCodes...),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ } else {
+ // however when there's an error the error's in the response
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(successfulStatusCodes...),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ }
+
+ return
+}
diff --git a/storage/2018-03-28/blob/containers/delete.go b/storage/2018-03-28/blob/containers/delete.go
new file mode 100644
index 0000000..3095829
--- /dev/null
+++ b/storage/2018-03-28/blob/containers/delete.go
@@ -0,0 +1,85 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete marks the specified container for deletion.
+// The container and any blobs contained within it are later deleted during garbage collection.
+func (client Client) Delete(ctx context.Context, accountName, containerName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "Delete", "`containerName` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, containerName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName string, containerName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-03-28/blob/containers/get_properties.go b/storage/2018-03-28/blob/containers/get_properties.go
new file mode 100644
index 0000000..1e308da
--- /dev/null
+++ b/storage/2018-03-28/blob/containers/get_properties.go
@@ -0,0 +1,124 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// GetProperties returns the properties for this Container without a Lease
+func (client Client) GetProperties(ctx context.Context, accountName, containerName string) (ContainerProperties, error) {
+ // If specified, Get Container Properties only succeeds if the container’s lease is active and matches this ID.
+ // If there is no active lease or the ID does not match, 412 (Precondition Failed) is returned.
+ return client.GetPropertiesWithLeaseID(ctx, accountName, containerName, "")
+}
+
+// GetPropertiesWithLeaseID returns the properties for this Container using the specified LeaseID
+func (client Client) GetPropertiesWithLeaseID(ctx context.Context, accountName, containerName, leaseID string) (result ContainerProperties, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "GetPropertiesWithLeaseID", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "GetPropertiesWithLeaseID", "`containerName` cannot be an empty string.")
+ }
+
+ req, err := client.GetPropertiesWithLeaseIDPreparer(ctx, accountName, containerName, leaseID)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "GetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetPropertiesWithLeaseIDSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "GetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetPropertiesWithLeaseIDResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "GetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPropertiesWithLeaseIDPreparer prepares the GetPropertiesWithLeaseID request.
+func (client Client) GetPropertiesWithLeaseIDPreparer(ctx context.Context, accountName, containerName, leaseID string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ // If specified, Get Container Properties only succeeds if the container’s lease is active and matches this ID.
+ // If there is no active lease or the ID does not match, 412 (Precondition Failed) is returned.
+ if leaseID != "" {
+ headers["x-ms-lease-id"] = leaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetPropertiesWithLeaseIDSender sends the GetPropertiesWithLeaseID request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetPropertiesWithLeaseIDSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetPropertiesWithLeaseIDResponder handles the response to the GetPropertiesWithLeaseID request. The method always
+// closes the http.Response Body.
+func (client Client) GetPropertiesWithLeaseIDResponder(resp *http.Response) (result ContainerProperties, err error) {
+ if resp != nil {
+ result.LeaseStatus = LeaseStatus(resp.Header.Get("x-ms-lease-status"))
+ result.LeaseState = LeaseState(resp.Header.Get("x-ms-lease-state"))
+ if result.LeaseStatus == Locked {
+ duration := LeaseDuration(resp.Header.Get("x-ms-lease-duration"))
+ result.LeaseDuration = &duration
+ }
+
+ // If this header is not returned in the response, the container is private to the account owner.
+ accessLevel := resp.Header.Get("x-ms-blob-public-access")
+ if accessLevel != "" {
+ result.AccessLevel = AccessLevel(accessLevel)
+ } else {
+ result.AccessLevel = Private
+ }
+
+ // we can't necessarily use strconv.ParseBool here since this could be nil (only in some API versions)
+ result.HasImmutabilityPolicy = strings.EqualFold(resp.Header.Get("x-ms-has-immutability-policy"), "true")
+ result.HasLegalHold = strings.EqualFold(resp.Header.Get("x-ms-has-legal-hold"), "true")
+
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/containers/lease_acquire.go b/storage/2018-03-28/blob/containers/lease_acquire.go
new file mode 100644
index 0000000..061c863
--- /dev/null
+++ b/storage/2018-03-28/blob/containers/lease_acquire.go
@@ -0,0 +1,115 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type AcquireLeaseInput struct {
+ // Specifies the duration of the lease, in seconds, or negative one (-1) for a lease that never expires.
+ // A non-infinite lease can be between 15 and 60 seconds
+ LeaseDuration int
+
+ ProposedLeaseID string
+}
+
+type AcquireLeaseResponse struct {
+ autorest.Response
+
+ LeaseID string
+}
+
+// AcquireLease establishes and manages a lock on a container for delete operations.
+func (client Client) AcquireLease(ctx context.Context, accountName, containerName string, input AcquireLeaseInput) (result AcquireLeaseResponse, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "AcquireLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "AcquireLease", "`containerName` cannot be an empty string.")
+ }
+ // An infinite lease duration is -1 seconds. A non-infinite lease can be between 15 and 60 seconds
+ if input.LeaseDuration != -1 && (input.LeaseDuration <= 15 || input.LeaseDuration >= 60) {
+ return result, validation.NewError("containers.Client", "AcquireLease", "`input.LeaseDuration` must be -1 (infinite), or between 15 and 60 seconds.")
+ }
+
+ req, err := client.AcquireLeasePreparer(ctx, accountName, containerName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "AcquireLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.AcquireLeaseSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "AcquireLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.AcquireLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "AcquireLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// AcquireLeasePreparer prepares the AcquireLease request.
+func (client Client) AcquireLeasePreparer(ctx context.Context, accountName string, containerName string, input AcquireLeaseInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ "comp": autorest.Encode("path", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "acquire",
+ "x-ms-lease-duration": input.LeaseDuration,
+ }
+
+ if input.ProposedLeaseID != "" {
+ headers["x-ms-proposed-lease-id"] = input.ProposedLeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// AcquireLeaseSender sends the AcquireLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) AcquireLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// AcquireLeaseResponder handles the response to the AcquireLease request. The method always
+// closes the http.Response Body.
+func (client Client) AcquireLeaseResponder(resp *http.Response) (result AcquireLeaseResponse, err error) {
+ if resp != nil {
+ result.LeaseID = resp.Header.Get("x-ms-lease-id")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/containers/lease_break.go b/storage/2018-03-28/blob/containers/lease_break.go
new file mode 100644
index 0000000..08acfb7
--- /dev/null
+++ b/storage/2018-03-28/blob/containers/lease_break.go
@@ -0,0 +1,129 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+ "strconv"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type BreakLeaseInput struct {
+ // For a break operation, proposed duration the lease should continue
+ // before it is broken, in seconds, between 0 and 60.
+ // This break period is only used if it is shorter than the time remaining on the lease.
+ // If longer, the time remaining on the lease is used.
+ // A new lease will not be available before the break period has expired,
+ // but the lease may be held for longer than the break period.
+ // If this header does not appear with a break operation, a fixed-duration lease breaks
+ // after the remaining lease period elapses, and an infinite lease breaks immediately.
+ BreakPeriod *int
+
+ LeaseID string
+}
+
+type BreakLeaseResponse struct {
+ autorest.Response
+
+ // Approximate time remaining in the lease period, in seconds.
+ // If the break is immediate, 0 is returned.
+ LeaseTime int
+}
+
+// BreakLease breaks a lock based on it's Lease ID
+func (client Client) BreakLease(ctx context.Context, accountName, containerName string, input BreakLeaseInput) (result BreakLeaseResponse, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "BreakLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "BreakLease", "`containerName` cannot be an empty string.")
+ }
+ if input.LeaseID == "" {
+ return result, validation.NewError("containers.Client", "BreakLease", "`input.LeaseID` cannot be an empty string.")
+ }
+
+ req, err := client.BreakLeasePreparer(ctx, accountName, containerName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "BreakLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.BreakLeaseSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "BreakLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.BreakLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "BreakLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// BreakLeasePreparer prepares the BreakLease request.
+func (client Client) BreakLeasePreparer(ctx context.Context, accountName string, containerName string, input BreakLeaseInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ "comp": autorest.Encode("path", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "break",
+ "x-ms-lease-id": input.LeaseID,
+ }
+
+ if input.BreakPeriod != nil {
+ headers["x-ms-lease-break-period"] = *input.BreakPeriod
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// BreakLeaseSender sends the BreakLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) BreakLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// BreakLeaseResponder handles the response to the BreakLease request. The method always
+// closes the http.Response Body.
+func (client Client) BreakLeaseResponder(resp *http.Response) (result BreakLeaseResponse, err error) {
+ if resp != nil {
+ leaseRaw := resp.Header.Get("x-ms-lease-time")
+ if leaseRaw != "" {
+ i, err := strconv.Atoi(leaseRaw)
+ if err == nil {
+ result.LeaseTime = i
+ }
+ }
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/containers/lease_change.go b/storage/2018-03-28/blob/containers/lease_change.go
new file mode 100644
index 0000000..dfbcb13
--- /dev/null
+++ b/storage/2018-03-28/blob/containers/lease_change.go
@@ -0,0 +1,111 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type ChangeLeaseInput struct {
+ ExistingLeaseID string
+ ProposedLeaseID string
+}
+
+type ChangeLeaseResponse struct {
+ autorest.Response
+
+ LeaseID string
+}
+
+// ChangeLease changes the lock from one Lease ID to another Lease ID
+func (client Client) ChangeLease(ctx context.Context, accountName, containerName string, input ChangeLeaseInput) (result ChangeLeaseResponse, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "ChangeLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "ChangeLease", "`containerName` cannot be an empty string.")
+ }
+ if input.ExistingLeaseID == "" {
+ return result, validation.NewError("containers.Client", "ChangeLease", "`input.ExistingLeaseID` cannot be an empty string.")
+ }
+ if input.ProposedLeaseID == "" {
+ return result, validation.NewError("containers.Client", "ChangeLease", "`input.ProposedLeaseID` cannot be an empty string.")
+ }
+
+ req, err := client.ChangeLeasePreparer(ctx, accountName, containerName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "ChangeLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ChangeLeaseSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "ChangeLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ChangeLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "ChangeLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ChangeLeasePreparer prepares the ChangeLease request.
+func (client Client) ChangeLeasePreparer(ctx context.Context, accountName string, containerName string, input ChangeLeaseInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ "comp": autorest.Encode("path", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "change",
+ "x-ms-lease-id": input.ExistingLeaseID,
+ "x-ms-proposed-lease-id": input.ProposedLeaseID,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ChangeLeaseSender sends the ChangeLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ChangeLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ChangeLeaseResponder handles the response to the ChangeLease request. The method always
+// closes the http.Response Body.
+func (client Client) ChangeLeaseResponder(resp *http.Response) (result ChangeLeaseResponse, err error) {
+ if resp != nil {
+ result.LeaseID = resp.Header.Get("x-ms-lease-id")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/containers/lease_release.go b/storage/2018-03-28/blob/containers/lease_release.go
new file mode 100644
index 0000000..fafcf98
--- /dev/null
+++ b/storage/2018-03-28/blob/containers/lease_release.go
@@ -0,0 +1,92 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// ReleaseLease releases the lock based on the Lease ID
+func (client Client) ReleaseLease(ctx context.Context, accountName, containerName, leaseID string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "ReleaseLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "ReleaseLease", "`containerName` cannot be an empty string.")
+ }
+ if leaseID == "" {
+ return result, validation.NewError("containers.Client", "ReleaseLease", "`leaseID` cannot be an empty string.")
+ }
+
+ req, err := client.ReleaseLeasePreparer(ctx, accountName, containerName, leaseID)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "ReleaseLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ReleaseLeaseSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "ReleaseLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ReleaseLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "ReleaseLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ReleaseLeasePreparer prepares the ReleaseLease request.
+func (client Client) ReleaseLeasePreparer(ctx context.Context, accountName string, containerName string, leaseID string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ "comp": autorest.Encode("path", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "release",
+ "x-ms-lease-id": leaseID,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ReleaseLeaseSender sends the ReleaseLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ReleaseLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ReleaseLeaseResponder handles the response to the ReleaseLease request. The method always
+// closes the http.Response Body.
+func (client Client) ReleaseLeaseResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/containers/lease_renew.go b/storage/2018-03-28/blob/containers/lease_renew.go
new file mode 100644
index 0000000..3fe1765
--- /dev/null
+++ b/storage/2018-03-28/blob/containers/lease_renew.go
@@ -0,0 +1,92 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// RenewLease renewes the lock based on the Lease ID
+func (client Client) RenewLease(ctx context.Context, accountName, containerName, leaseID string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "RenewLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "RenewLease", "`containerName` cannot be an empty string.")
+ }
+ if leaseID == "" {
+ return result, validation.NewError("containers.Client", "RenewLease", "`leaseID` cannot be an empty string.")
+ }
+
+ req, err := client.RenewLeasePreparer(ctx, accountName, containerName, leaseID)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "RenewLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.RenewLeaseSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "RenewLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.RenewLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "RenewLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// RenewLeasePreparer prepares the RenewLease request.
+func (client Client) RenewLeasePreparer(ctx context.Context, accountName string, containerName string, leaseID string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ "comp": autorest.Encode("path", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "renew",
+ "x-ms-lease-id": leaseID,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// RenewLeaseSender sends the RenewLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) RenewLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// RenewLeaseResponder handles the response to the RenewLease request. The method always
+// closes the http.Response Body.
+func (client Client) RenewLeaseResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/containers/lifecycle_test.go b/storage/2018-03-28/blob/containers/lifecycle_test.go
new file mode 100644
index 0000000..389c773
--- /dev/null
+++ b/storage/2018-03-28/blob/containers/lifecycle_test.go
@@ -0,0 +1,174 @@
+package containers
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestContainerLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ containersClient := NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithAuthorizer(containersClient.Client, storageAuth)
+
+ // first let's test an empty container
+ input := CreateInput{}
+ _, err = containersClient.Create(ctx, accountName, containerName, input)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+
+ container, err := containersClient.GetProperties(ctx, accountName, containerName)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error retrieving: %s", err))
+ }
+
+ if container.AccessLevel != Private {
+ t.Fatalf("Expected Access Level to be Private but got %q", container.AccessLevel)
+ }
+ if len(container.MetaData) != 0 {
+ t.Fatalf("Expected MetaData to be empty but got: %s", container.MetaData)
+ }
+ if container.LeaseStatus != Unlocked {
+ t.Fatalf("Expected Container Lease to be Unlocked but was: %s", container.LeaseStatus)
+ }
+
+ // then update the metadata
+ metaData := map[string]string{
+ "dont": "kill-my-vibe",
+ }
+ _, err = containersClient.SetMetaData(ctx, accountName, containerName, metaData)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error updating metadata: %s", err))
+ }
+
+ // give azure time to replicate
+ time.Sleep(2 * time.Second)
+
+ // then assert that
+ container, err = containersClient.GetProperties(ctx, accountName, containerName)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error re-retrieving: %s", err))
+ }
+ if len(container.MetaData) != 1 {
+ t.Fatalf("Expected 1 item in the metadata but got: %s", container.MetaData)
+ }
+ if container.MetaData["dont"] != "kill-my-vibe" {
+ t.Fatalf("Expected `kill-my-vibe` but got %q", container.MetaData["dont"])
+ }
+ if container.AccessLevel != Private {
+ t.Fatalf("Expected Access Level to be Private but got %q", container.AccessLevel)
+ }
+ if container.LeaseStatus != Unlocked {
+ t.Fatalf("Expected Container Lease to be Unlocked but was: %s", container.LeaseStatus)
+ }
+
+ // then update the ACL
+ _, err = containersClient.SetAccessControl(ctx, accountName, containerName, Blob)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error updating ACL's: %s", err))
+ }
+
+ // give azure some time to replicate
+ time.Sleep(2 * time.Second)
+
+ // then assert that
+ container, err = containersClient.GetProperties(ctx, accountName, containerName)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error re-retrieving: %s", err))
+ }
+ if container.AccessLevel != Blob {
+ t.Fatalf("Expected Access Level to be Blob but got %q", container.AccessLevel)
+ }
+ if len(container.MetaData) != 1 {
+ t.Fatalf("Expected 1 item in the metadata but got: %s", container.MetaData)
+ }
+ if container.LeaseStatus != Unlocked {
+ t.Fatalf("Expected Container Lease to be Unlocked but was: %s", container.LeaseStatus)
+ }
+
+ // acquire a lease for 30s
+ acquireLeaseInput := AcquireLeaseInput{
+ LeaseDuration: 30,
+ }
+ acquireLeaseResp, err := containersClient.AcquireLease(ctx, accountName, containerName, acquireLeaseInput)
+ if err != nil {
+ t.Fatalf("Error acquiring lease: %s", err)
+ }
+ t.Logf("[DEBUG] Lease ID: %s", acquireLeaseResp.LeaseID)
+
+ // we should then be able to update the ID
+ t.Logf("[DEBUG] Changing lease..")
+ updateLeaseInput := ChangeLeaseInput{
+ ExistingLeaseID: acquireLeaseResp.LeaseID,
+ ProposedLeaseID: "aaaabbbb-aaaa-bbbb-cccc-aaaabbbbcccc",
+ }
+ updateLeaseResp, err := containersClient.ChangeLease(ctx, accountName, containerName, updateLeaseInput)
+ if err != nil {
+ t.Fatalf("Error changing lease: %s", err)
+ }
+
+ // then renew it
+ _, err = containersClient.RenewLease(ctx, accountName, containerName, updateLeaseResp.LeaseID)
+ if err != nil {
+ t.Fatalf("Error renewing lease: %s", err)
+ }
+
+ // and then give it a timeout
+ breakPeriod := 20
+ breakLeaseInput := BreakLeaseInput{
+ LeaseID: updateLeaseResp.LeaseID,
+ BreakPeriod: &breakPeriod,
+ }
+ breakLeaseResp, err := containersClient.BreakLease(ctx, accountName, containerName, breakLeaseInput)
+ if err != nil {
+ t.Fatalf("Error breaking lease: %s", err)
+ }
+ if breakLeaseResp.LeaseTime == 0 {
+ t.Fatalf("Lease broke immediately when should have waited: %d", breakLeaseResp.LeaseTime)
+ }
+
+ // and finally ditch it
+ _, err = containersClient.ReleaseLease(ctx, accountName, containerName, updateLeaseResp.LeaseID)
+ if err != nil {
+ t.Fatalf("Error releasing lease: %s", err)
+ }
+
+ t.Logf("[DEBUG] Listing blobs in the container..")
+ listInput := ListBlobsInput{}
+ listResult, err := containersClient.ListBlobs(ctx, accountName, containerName, listInput)
+ if err != nil {
+ t.Fatalf("Error listing blobs: %s", err)
+ }
+
+ if len(listResult.Blobs.Blobs) != 0 {
+ t.Fatalf("Expected there to be no blobs in the container but got %d", len(listResult.Blobs.Blobs))
+ }
+
+ t.Logf("[DEBUG] Deleting..")
+ _, err = containersClient.Delete(ctx, accountName, containerName)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error deleting: %s", err))
+ }
+}
diff --git a/storage/2018-03-28/blob/containers/list_blobs.go b/storage/2018-03-28/blob/containers/list_blobs.go
new file mode 100644
index 0000000..82797d0
--- /dev/null
+++ b/storage/2018-03-28/blob/containers/list_blobs.go
@@ -0,0 +1,179 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type ListBlobsInput struct {
+ Delimiter *string
+ Include *[]Dataset
+ Marker *string
+ MaxResults *int
+ Prefix *string
+}
+
+type ListBlobsResult struct {
+ autorest.Response
+
+ Delimiter string `xml:"Delimiter"`
+ Marker string `xml:"Marker"`
+ MaxResults int `xml:"MaxResults"`
+ NextMarker *string `xml:"NextMarker,omitempty"`
+ Prefix string `xml:"Prefix"`
+ Blobs Blobs `xml:"Blobs"`
+}
+
+type Blobs struct {
+ Blobs []BlobDetails `xml:"Blob"`
+ BlobPrefix *BlobPrefix `xml:"BlobPrefix"`
+}
+
+type BlobDetails struct {
+ Name string `xml:"Name"`
+ Deleted bool `xml:"Deleted,omitempty"`
+ MetaData map[string]interface{} `map:"Metadata,omitempty"`
+ Properties *BlobProperties `xml:"Properties,omitempty"`
+ Snapshot *string `xml:"Snapshot,omitempty"`
+}
+
+type BlobProperties struct {
+ AccessTier *string `xml:"AccessTier,omitempty"`
+ AccessTierInferred *bool `xml:"AccessTierInferred,omitempty"`
+ AccessTierChangeTime *string `xml:"AccessTierChangeTime,omitempty"`
+ BlobType *string `xml:"BlobType,omitempty"`
+ BlobSequenceNumber *string `xml:"x-ms-blob-sequence-number,omitempty"`
+ CacheControl *string `xml:"Cache-Control,omitempty"`
+ ContentEncoding *string `xml:"ContentEncoding,omitempty"`
+ ContentLanguage *string `xml:"Content-Language,omitempty"`
+ ContentLength *int64 `xml:"Content-Length,omitempty"`
+ ContentMD5 *string `xml:"Content-MD5,omitempty"`
+ ContentType *string `xml:"Content-Type,omitempty"`
+ CopyCompletionTime *string `xml:"CopyCompletionTime,omitempty"`
+ CopyId *string `xml:"CopyId,omitempty"`
+ CopyStatus *string `xml:"CopyStatus,omitempty"`
+ CopySource *string `xml:"CopySource,omitempty"`
+ CopyProgress *string `xml:"CopyProgress,omitempty"`
+ CopyStatusDescription *string `xml:"CopyStatusDescription,omitempty"`
+ CreationTime *string `xml:"CreationTime,omitempty"`
+ ETag *string `xml:"Etag,omitempty"`
+ DeletedTime *string `xml:"DeletedTime,omitempty"`
+ IncrementalCopy *bool `xml:"IncrementalCopy,omitempty"`
+ LastModified *string `xml:"Last-Modified,omitempty"`
+ LeaseDuration *string `xml:"LeaseDuration,omitempty"`
+ LeaseState *string `xml:"LeaseState,omitempty"`
+ LeaseStatus *string `xml:"LeaseStatus,omitempty"`
+ RemainingRetentionDays *string `xml:"RemainingRetentionDays,omitempty"`
+ ServerEncrypted *bool `xml:"ServerEncrypted,omitempty"`
+}
+
+type BlobPrefix struct {
+ Name string `xml:"Name"`
+}
+
+// ListBlobs lists the blobs matching the specified query within the specified Container
+func (client Client) ListBlobs(ctx context.Context, accountName, containerName string, input ListBlobsInput) (result ListBlobsResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "ListBlobs", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "ListBlobs", "`containerName` cannot be an empty string.")
+ }
+ if input.MaxResults != nil && (*input.MaxResults <= 0 || *input.MaxResults > 5000) {
+ return result, validation.NewError("containers.Client", "ListBlobs", "`input.MaxResults` can either be nil or between 0 and 5000.")
+ }
+
+ req, err := client.ListBlobsPreparer(ctx, accountName, containerName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "ListBlobs", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ListBlobsSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "ListBlobs", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ListBlobsResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "ListBlobs", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ListBlobsPreparer prepares the ListBlobs request.
+func (client Client) ListBlobsPreparer(ctx context.Context, accountName, containerName string, input ListBlobsInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "list"),
+ "restype": autorest.Encode("query", "container"),
+ }
+
+ if input.Delimiter != nil {
+ queryParameters["delimiter"] = autorest.Encode("query", *input.Delimiter)
+ }
+ if input.Include != nil {
+ vals := make([]string, 0)
+ for _, v := range *input.Include {
+ vals = append(vals, string(v))
+ }
+ include := strings.Join(vals, ",")
+ queryParameters["include"] = autorest.Encode("query", include)
+ }
+ if input.Marker != nil {
+ queryParameters["marker"] = autorest.Encode("query", *input.Marker)
+ }
+ if input.MaxResults != nil {
+ queryParameters["maxresults"] = autorest.Encode("query", *input.MaxResults)
+ }
+ if input.Prefix != nil {
+ queryParameters["prefix"] = autorest.Encode("query", *input.Prefix)
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ListBlobsSender sends the ListBlobs request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ListBlobsSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ListBlobsResponder handles the response to the ListBlobs request. The method always
+// closes the http.Response Body.
+func (client Client) ListBlobsResponder(resp *http.Response) (result ListBlobsResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/containers/models.go b/storage/2018-03-28/blob/containers/models.go
new file mode 100644
index 0000000..adba368
--- /dev/null
+++ b/storage/2018-03-28/blob/containers/models.go
@@ -0,0 +1,75 @@
+package containers
+
+import "github.com/Azure/go-autorest/autorest"
+
+type AccessLevel string
+
+var (
+ // Blob specifies public read access for blobs.
+ // Blob data within this container can be read via anonymous request,
+ // but container data is not available.
+ // Clients cannot enumerate blobs within the container via anonymous request.
+ Blob AccessLevel = "blob"
+
+ // Container specifies full public read access for container and blob data.
+ // Clients can enumerate blobs within the container via anonymous request,
+ // but cannot enumerate containers within the storage account.
+ Container AccessLevel = "container"
+
+ // Private specifies that container data is private to the account owner
+ Private AccessLevel = ""
+)
+
+type ContainerProperties struct {
+ autorest.Response
+
+ AccessLevel AccessLevel
+ LeaseStatus LeaseStatus
+ LeaseState LeaseState
+ LeaseDuration *LeaseDuration
+ MetaData map[string]string
+ HasImmutabilityPolicy bool
+ HasLegalHold bool
+}
+
+type Dataset string
+
+var (
+ Copy Dataset = "copy"
+ Deleted Dataset = "deleted"
+ MetaData Dataset = "metadata"
+ Snapshots Dataset = "snapshots"
+ UncommittedBlobs Dataset = "uncommittedblobs"
+)
+
+type ErrorResponse struct {
+ Code *string `xml:"Code"`
+ Message *string `xml:"Message"`
+}
+
+type LeaseDuration string
+
+var (
+ // If this lease is for a Fixed Duration
+ Fixed LeaseDuration = "fixed"
+
+ // If this lease is for an Indefinite Duration
+ Infinite LeaseDuration = "infinite"
+)
+
+type LeaseState string
+
+var (
+ Available LeaseState = "available"
+ Breaking LeaseState = "breaking"
+ Broken LeaseState = "broken"
+ Expired LeaseState = "expired"
+ Leased LeaseState = "leased"
+)
+
+type LeaseStatus string
+
+var (
+ Locked LeaseStatus = "locked"
+ Unlocked LeaseStatus = "unlocked"
+)
diff --git a/storage/2018-03-28/blob/containers/resource_id.go b/storage/2018-03-28/blob/containers/resource_id.go
new file mode 100644
index 0000000..a5bfd6e
--- /dev/null
+++ b/storage/2018-03-28/blob/containers/resource_id.go
@@ -0,0 +1,46 @@
+package containers
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Container
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, containerName string) string {
+ domain := endpoints.GetBlobEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s", domain, containerName)
+}
+
+type ResourceID struct {
+ AccountName string
+ ContainerName string
+}
+
+// ParseResourceID parses the Resource ID and returns an object which can be used
+// to interact with the Container Resource
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://foo.blob.core.windows.net/Bar
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ containerName := strings.TrimPrefix(uri.Path, "/")
+ return &ResourceID{
+ AccountName: *accountName,
+ ContainerName: containerName,
+ }, nil
+}
diff --git a/storage/2018-03-28/blob/containers/resource_id_test.go b/storage/2018-03-28/blob/containers/resource_id_test.go
new file mode 100644
index 0000000..e27bc9d
--- /dev/null
+++ b/storage/2018-03-28/blob/containers/resource_id_test.go
@@ -0,0 +1,79 @@
+package containers
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.blob.core.chinacloudapi.cn/container1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.blob.core.cloudapi.de/container1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.blob.core.windows.net/container1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.blob.core.usgovcloudapi.net/container1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "container1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.blob.core.chinacloudapi.cn/container1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.blob.core.cloudapi.de/container1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.blob.core.windows.net/container1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.blob.core.usgovcloudapi.net/container1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected the account name to be `account1` but got %q", actual.AccountName)
+ }
+
+ if actual.ContainerName != "container1" {
+ t.Fatalf("Expected the container name to be `container1` but got %q", actual.ContainerName)
+ }
+ }
+}
diff --git a/storage/2018-03-28/blob/containers/set_acl.go b/storage/2018-03-28/blob/containers/set_acl.go
new file mode 100644
index 0000000..fcf4e10
--- /dev/null
+++ b/storage/2018-03-28/blob/containers/set_acl.go
@@ -0,0 +1,100 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// SetAccessControl sets the Access Control for a Container without a Lease ID
+func (client Client) SetAccessControl(ctx context.Context, accountName, containerName string, level AccessLevel) (autorest.Response, error) {
+ return client.SetAccessControlWithLeaseID(ctx, accountName, containerName, "", level)
+}
+
+// SetAccessControlWithLeaseID sets the Access Control for a Container using the specified Lease ID
+func (client Client) SetAccessControlWithLeaseID(ctx context.Context, accountName, containerName, leaseID string, level AccessLevel) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "SetAccessControl", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "SetAccessControl", "`containerName` cannot be an empty string.")
+ }
+
+ req, err := client.SetAccessControlWithLeaseIDPreparer(ctx, accountName, containerName, leaseID, level)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "SetAccessControl", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetAccessControlWithLeaseIDSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "SetAccessControl", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetAccessControlWithLeaseIDResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "SetAccessControl", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetAccessControlWithLeaseIDPreparer prepares the SetAccessControlWithLeaseID request.
+func (client Client) SetAccessControlWithLeaseIDPreparer(ctx context.Context, accountName, containerName, leaseID string, level AccessLevel) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "acl"),
+ "restype": autorest.Encode("path", "container"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = client.setAccessLevelIntoHeaders(headers, level)
+
+ // If specified, Get Container Properties only succeeds if the container’s lease is active and matches this ID.
+ // If there is no active lease or the ID does not match, 412 (Precondition Failed) is returned.
+ if leaseID != "" {
+ headers["x-ms-lease-id"] = leaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetAccessControlWithLeaseIDSender sends the SetAccessControlWithLeaseID request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetAccessControlWithLeaseIDSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetAccessControlWithLeaseIDResponder handles the response to the SetAccessControlWithLeaseID request. The method always
+// closes the http.Response Body.
+func (client Client) SetAccessControlWithLeaseIDResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/containers/set_metadata.go b/storage/2018-03-28/blob/containers/set_metadata.go
new file mode 100644
index 0000000..fb9e07f
--- /dev/null
+++ b/storage/2018-03-28/blob/containers/set_metadata.go
@@ -0,0 +1,105 @@
+package containers
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// SetMetaData sets the specified MetaData on the Container without a Lease ID
+func (client Client) SetMetaData(ctx context.Context, accountName, containerName string, metaData map[string]string) (autorest.Response, error) {
+ return client.SetMetaDataWithLeaseID(ctx, accountName, containerName, "", metaData)
+}
+
+// SetMetaDataWithLeaseID sets the specified MetaData on the Container using the specified Lease ID
+func (client Client) SetMetaDataWithLeaseID(ctx context.Context, accountName, containerName, leaseID string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "SetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "SetMetaData", "`containerName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("containers.Client", "SetMetaData", fmt.Sprintf("`metaData` is not valid: %s.", err))
+ }
+
+ req, err := client.SetMetaDataWithLeaseIDPreparer(ctx, accountName, containerName, leaseID, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "SetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetMetaDataWithLeaseIDSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "SetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetMetaDataWithLeaseIDResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "SetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetMetaDataWithLeaseIDPreparer prepares the SetMetaDataWithLeaseID request.
+func (client Client) SetMetaDataWithLeaseIDPreparer(ctx context.Context, accountName, containerName, leaseID string, metaData map[string]string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "metadata"),
+ "restype": autorest.Encode("path", "container"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ // If specified, Get Container Properties only succeeds if the container’s lease is active and matches this ID.
+ // If there is no active lease or the ID does not match, 412 (Precondition Failed) is returned.
+ if leaseID != "" {
+ headers["x-ms-lease-id"] = leaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetMetaDataWithLeaseIDSender sends the SetMetaDataWithLeaseID request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetMetaDataWithLeaseIDSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetMetaDataWithLeaseIDResponder handles the response to the SetMetaDataWithLeaseID request. The method always
+// closes the http.Response Body.
+func (client Client) SetMetaDataWithLeaseIDResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/blob/containers/version.go b/storage/2018-03-28/blob/containers/version.go
new file mode 100644
index 0000000..048d103
--- /dev/null
+++ b/storage/2018-03-28/blob/containers/version.go
@@ -0,0 +1,14 @@
+package containers
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2018-03-28"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2018-03-28/file/directories/README.md b/storage/2018-03-28/file/directories/README.md
new file mode 100644
index 0000000..804f11b
--- /dev/null
+++ b/storage/2018-03-28/file/directories/README.md
@@ -0,0 +1,44 @@
+## File Storage Directories SDK for API version 2018-03-28
+
+This package allows you to interact with the Directories File Storage API
+
+### Supported Authorizers
+
+* Azure Active Directory (for the Resource Endpoint `https://storage.azure.com`)
+* SharedKeyLite (Blob, File & Queue)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/file/directories"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ shareName := "myshare"
+ directoryName := "myfiles"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ directoriesClient := directories.New()
+ directoriesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ metadata := map[string]string{
+ "hello": "world",
+ }
+ if _, err := directoriesClient.Create(ctx, accountName, shareName, directoryName, metadata); err != nil {
+ return fmt.Errorf("Error creating Directory: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2018-03-28/file/directories/client.go b/storage/2018-03-28/file/directories/client.go
new file mode 100644
index 0000000..bf2d315
--- /dev/null
+++ b/storage/2018-03-28/file/directories/client.go
@@ -0,0 +1,25 @@
+package directories
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for File Storage Shares.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2018-03-28/file/directories/create.go b/storage/2018-03-28/file/directories/create.go
new file mode 100644
index 0000000..93f5c82
--- /dev/null
+++ b/storage/2018-03-28/file/directories/create.go
@@ -0,0 +1,101 @@
+package directories
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// Create creates a new directory under the specified share or parent directory.
+func (client Client) Create(ctx context.Context, accountName, shareName, path string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("directories.Client", "Create", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("directories.Client", "Create", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("directories.Client", "Create", "`shareName` must be a lower-cased string.")
+ }
+ if path == "" {
+ return result, validation.NewError("directories.Client", "Create", "`path` cannot be an empty string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("directories.Client", "Create", fmt.Sprintf("`metadata` is not valid: %s.", err))
+ }
+
+ req, err := client.CreatePreparer(ctx, accountName, shareName, path, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "Create", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "directories.Client", "Create", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "Create", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreatePreparer prepares the Create request.
+func (client Client) CreatePreparer(ctx context.Context, accountName, shareName, path string, metaData map[string]string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "directory"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSender sends the Create request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateResponder handles the response to the Create request. The method always
+// closes the http.Response Body.
+func (client Client) CreateResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/directories/delete.go b/storage/2018-03-28/file/directories/delete.go
new file mode 100644
index 0000000..9443c25
--- /dev/null
+++ b/storage/2018-03-28/file/directories/delete.go
@@ -0,0 +1,95 @@
+package directories
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete removes the specified empty directory
+// Note that the directory must be empty before it can be deleted.
+func (client Client) Delete(ctx context.Context, accountName, shareName, path string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("directories.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("directories.Client", "Delete", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("directories.Client", "Delete", "`shareName` must be a lower-cased string.")
+ }
+ if path == "" {
+ return result, validation.NewError("directories.Client", "Delete", "`path` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, shareName, path)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "directories.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, shareName, path string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "directory"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/directories/get.go b/storage/2018-03-28/file/directories/get.go
new file mode 100644
index 0000000..817d680
--- /dev/null
+++ b/storage/2018-03-28/file/directories/get.go
@@ -0,0 +1,112 @@
+package directories
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetResult struct {
+ autorest.Response
+
+ // A set of name-value pairs that contain metadata for the directory.
+ MetaData map[string]string
+
+ // The value of this header is set to true if the directory metadata is completely
+ // encrypted using the specified algorithm. Otherwise, the value is set to false.
+ DirectoryMetaDataEncrypted bool
+}
+
+// Get returns all system properties for the specified directory,
+// and can also be used to check the existence of a directory.
+func (client Client) Get(ctx context.Context, accountName, shareName, path string) (result GetResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("directories.Client", "Get", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("directories.Client", "Get", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("directories.Client", "Get", "`shareName` must be a lower-cased string.")
+ }
+ if path == "" {
+ return result, validation.NewError("directories.Client", "Get", "`path` cannot be an empty string.")
+ }
+
+ req, err := client.GetPreparer(ctx, accountName, shareName, path)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "Get", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "directories.Client", "Get", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "Get", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPreparer prepares the Get request.
+func (client Client) GetPreparer(ctx context.Context, accountName, shareName, path string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "directory"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetSender sends the Get request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetResponder handles the response to the Get request. The method always
+// closes the http.Response Body.
+func (client Client) GetResponder(resp *http.Response) (result GetResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ result.DirectoryMetaDataEncrypted = strings.EqualFold(resp.Header.Get("x-ms-server-encrypted"), "true")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/directories/lifecycle_test.go b/storage/2018-03-28/file/directories/lifecycle_test.go
new file mode 100644
index 0000000..aaf5d47
--- /dev/null
+++ b/storage/2018-03-28/file/directories/lifecycle_test.go
@@ -0,0 +1,107 @@
+package directories
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/file/shares"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestDirectoriesLifeCycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := shares.NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ directoriesClient := NewWithEnvironment(client.Environment)
+ directoriesClient.Client = client.PrepareWithAuthorizer(directoriesClient.Client, storageAuth)
+
+ input := shares.CreateInput{
+ QuotaInGB: 1,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+ defer sharesClient.Delete(ctx, accountName, shareName, true)
+
+ metaData := map[string]string{
+ "hello": "world",
+ }
+
+ log.Printf("[DEBUG] Creating Top Level..")
+ if _, err := directoriesClient.Create(ctx, accountName, shareName, "hello", metaData); err != nil {
+ t.Fatalf("Error creating Top Level Directory: %s", err)
+ }
+
+ log.Printf("[DEBUG] Creating Inner..")
+ if _, err := directoriesClient.Create(ctx, accountName, shareName, "hello/there", metaData); err != nil {
+ t.Fatalf("Error creating Inner Directory: %s", err)
+ }
+
+ log.Printf("[DEBUG] Retrieving share")
+ innerDir, err := directoriesClient.Get(ctx, accountName, shareName, "hello/there")
+ if err != nil {
+ t.Fatalf("Error retrieving Inner Directory: %s", err)
+ }
+
+ if innerDir.DirectoryMetaDataEncrypted != true {
+ t.Fatalf("Expected MetaData to be encrypted but got: %t", innerDir.DirectoryMetaDataEncrypted)
+ }
+
+ if len(innerDir.MetaData) != 1 {
+ t.Fatalf("Expected MetaData to contain 1 item but got %d", len(innerDir.MetaData))
+ }
+ if innerDir.MetaData["hello"] != "world" {
+ t.Fatalf("Expected MetaData `hello` to be `world`: %s", innerDir.MetaData["hello"])
+ }
+
+ log.Printf("[DEBUG] Setting MetaData")
+ updatedMetaData := map[string]string{
+ "panda": "pops",
+ }
+ if _, err := directoriesClient.SetMetaData(ctx, accountName, shareName, "hello/there", updatedMetaData); err != nil {
+ t.Fatalf("Error updating MetaData: %s", err)
+ }
+
+ log.Printf("[DEBUG] Retrieving MetaData")
+ retrievedMetaData, err := directoriesClient.GetMetaData(ctx, accountName, shareName, "hello/there")
+ if err != nil {
+ t.Fatalf("Error retrieving the updated metadata: %s", err)
+ }
+ if len(retrievedMetaData.MetaData) != 1 {
+ t.Fatalf("Expected the updated metadata to have 1 item but got %d", len(retrievedMetaData.MetaData))
+ }
+ if retrievedMetaData.MetaData["panda"] != "pops" {
+ t.Fatalf("Expected the metadata `panda` to be `pops` but got %q", retrievedMetaData.MetaData["panda"])
+ }
+
+ t.Logf("[DEBUG] Deleting Inner..")
+ if _, err := directoriesClient.Delete(ctx, accountName, shareName, "hello/there"); err != nil {
+ t.Fatalf("Error deleting Inner Directory: %s", err)
+ }
+
+ t.Logf("[DEBUG] Deleting Top Level..")
+ if _, err := directoriesClient.Delete(ctx, accountName, shareName, "hello"); err != nil {
+ t.Fatalf("Error deleting Top Level Directory: %s", err)
+ }
+}
diff --git a/storage/2018-03-28/file/directories/metadata_get.go b/storage/2018-03-28/file/directories/metadata_get.go
new file mode 100644
index 0000000..173716d
--- /dev/null
+++ b/storage/2018-03-28/file/directories/metadata_get.go
@@ -0,0 +1,106 @@
+package directories
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetMetaDataResult struct {
+ autorest.Response
+
+ MetaData map[string]string
+}
+
+// GetMetaData returns all user-defined metadata for the specified directory
+func (client Client) GetMetaData(ctx context.Context, accountName, shareName, path string) (result GetMetaDataResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("directories.Client", "GetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("directories.Client", "GetMetaData", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("directories.Client", "GetMetaData", "`shareName` must be a lower-cased string.")
+ }
+ if path == "" {
+ return result, validation.NewError("directories.Client", "GetMetaData", "`path` cannot be an empty string.")
+ }
+
+ req, err := client.GetMetaDataPreparer(ctx, accountName, shareName, path)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "GetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetMetaDataSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "directories.Client", "GetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "GetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetMetaDataPreparer prepares the GetMetaData request.
+func (client Client) GetMetaDataPreparer(ctx context.Context, accountName, shareName, path string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "directory"),
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetMetaDataSender sends the GetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetMetaDataResponder handles the response to the GetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) GetMetaDataResponder(resp *http.Response) (result GetMetaDataResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/directories/metadata_set.go b/storage/2018-03-28/file/directories/metadata_set.go
new file mode 100644
index 0000000..cb13312
--- /dev/null
+++ b/storage/2018-03-28/file/directories/metadata_set.go
@@ -0,0 +1,102 @@
+package directories
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// SetMetaData updates user defined metadata for the specified directory
+func (client Client) SetMetaData(ctx context.Context, accountName, shareName, path string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("directories.Client", "SetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("directories.Client", "SetMetaData", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("directories.Client", "SetMetaData", "`shareName` must be a lower-cased string.")
+ }
+ if path == "" {
+ return result, validation.NewError("directories.Client", "SetMetaData", "`path` cannot be an empty string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("directories.Client", "SetMetaData", fmt.Sprintf("`metaData` is not valid: %s.", err))
+ }
+
+ req, err := client.SetMetaDataPreparer(ctx, accountName, shareName, path, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "SetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetMetaDataSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "directories.Client", "SetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "SetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetMetaDataPreparer prepares the SetMetaData request.
+func (client Client) SetMetaDataPreparer(ctx context.Context, accountName, shareName, path string, metaData map[string]string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "directory"),
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetMetaDataSender sends the SetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetMetaDataResponder handles the response to the SetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) SetMetaDataResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/directories/resource_id.go b/storage/2018-03-28/file/directories/resource_id.go
new file mode 100644
index 0000000..44607c4
--- /dev/null
+++ b/storage/2018-03-28/file/directories/resource_id.go
@@ -0,0 +1,56 @@
+package directories
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Directory
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, shareName, directoryName string) string {
+ domain := endpoints.GetFileEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s/%s", domain, shareName, directoryName)
+}
+
+type ResourceID struct {
+ AccountName string
+ DirectoryName string
+ ShareName string
+}
+
+// ParseResourceID parses the Resource ID into an Object
+// which can be used to interact with the Directory within the File Share
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://foo.file.core.windows.net/Bar/Folder
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ path := strings.TrimPrefix(uri.Path, "/")
+ segments := strings.Split(path, "/")
+ if len(segments) == 0 {
+ return nil, fmt.Errorf("Expected the path to contain segments but got none")
+ }
+
+ shareName := segments[0]
+ directoryName := strings.TrimPrefix(path, shareName)
+ directoryName = strings.TrimPrefix(directoryName, "/")
+ return &ResourceID{
+ AccountName: *accountName,
+ ShareName: shareName,
+ DirectoryName: directoryName,
+ }, nil
+}
diff --git a/storage/2018-03-28/file/directories/resource_id_test.go b/storage/2018-03-28/file/directories/resource_id_test.go
new file mode 100644
index 0000000..0be800d
--- /dev/null
+++ b/storage/2018-03-28/file/directories/resource_id_test.go
@@ -0,0 +1,81 @@
+package directories
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.file.core.chinacloudapi.cn/share1/directory1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.file.core.cloudapi.de/share1/directory1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.file.core.windows.net/share1/directory1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.file.core.usgovcloudapi.net/share1/directory1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "share1", "directory1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.file.core.chinacloudapi.cn/share1/directory1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.file.core.cloudapi.de/share1/directory1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.file.core.windows.net/share1/directory1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.file.core.usgovcloudapi.net/share1/directory1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.ShareName != "share1" {
+ t.Fatalf("Expected Share Name to be `share1` but got %q", actual.ShareName)
+ }
+ if actual.DirectoryName != "directory1" {
+ t.Fatalf("Expected Directory Name to be `directory1` but got %q", actual.DirectoryName)
+ }
+ }
+}
diff --git a/storage/2018-03-28/file/directories/version.go b/storage/2018-03-28/file/directories/version.go
new file mode 100644
index 0000000..7940bde
--- /dev/null
+++ b/storage/2018-03-28/file/directories/version.go
@@ -0,0 +1,14 @@
+package directories
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2018-03-28"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2018-03-28/file/files/README.md b/storage/2018-03-28/file/files/README.md
new file mode 100644
index 0000000..240d617
--- /dev/null
+++ b/storage/2018-03-28/file/files/README.md
@@ -0,0 +1,43 @@
+## File Storage Files SDK for API version 2018-03-28
+
+This package allows you to interact with the Files File Storage API
+
+### Supported Authorizers
+
+* Azure Active Directory (for the Resource Endpoint `https://storage.azure.com`)
+* SharedKeyLite (Blob, File & Queue)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/file/files"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ shareName := "myshare"
+ directoryName := "myfiles"
+ fileName := "example.txt"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ filesClient := files.New()
+ filesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ input := files.CreateInput{}
+ if _, err := filesClient.Create(ctx, accountName, shareName, directoryName, fileName, input); err != nil {
+ return fmt.Errorf("Error creating File: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2018-03-28/file/files/client.go b/storage/2018-03-28/file/files/client.go
new file mode 100644
index 0000000..ecca815
--- /dev/null
+++ b/storage/2018-03-28/file/files/client.go
@@ -0,0 +1,25 @@
+package files
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for File Storage Shares.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2018-03-28/file/files/copy.go b/storage/2018-03-28/file/files/copy.go
new file mode 100644
index 0000000..31768b3
--- /dev/null
+++ b/storage/2018-03-28/file/files/copy.go
@@ -0,0 +1,132 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type CopyInput struct {
+ // Specifies the URL of the source file or blob, up to 2 KB in length.
+ //
+ // To copy a file to another file within the same storage account, you may use Shared Key to authenticate
+ // the source file. If you are copying a file from another storage account, or if you are copying a blob from
+ // the same storage account or another storage account, then you must authenticate the source file or blob using a
+ // shared access signature. If the source is a public blob, no authentication is required to perform the copy
+ // operation. A file in a share snapshot can also be specified as a copy source.
+ CopySource string
+
+ MetaData map[string]string
+}
+
+type CopyResult struct {
+ autorest.Response
+
+ // The CopyID, which can be passed to AbortCopy to abort the copy.
+ CopyID string
+
+ // Either `success` or `pending`
+ CopySuccess string
+}
+
+// Copy copies a blob or file to a destination file within the storage account asynchronously.
+func (client Client) Copy(ctx context.Context, accountName, shareName, path, fileName string, input CopyInput) (result CopyResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "Copy", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "Copy", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "Copy", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "Copy", "`fileName` cannot be an empty string.")
+ }
+ if input.CopySource == "" {
+ return result, validation.NewError("files.Client", "Copy", "`input.CopySource` cannot be an empty string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("files.Client", "Copy", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.CopyPreparer(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "Copy", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CopySender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "Copy", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CopyResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "Copy", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CopyPreparer prepares the Copy request.
+func (client Client) CopyPreparer(ctx context.Context, accountName, shareName, path, fileName string, input CopyInput) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-copy-source": input.CopySource,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CopySender sends the Copy request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CopySender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CopyResponder handles the response to the Copy request. The method always
+// closes the http.Response Body.
+func (client Client) CopyResponder(resp *http.Response) (result CopyResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.CopyID = resp.Header.Get("x-ms-copy-id")
+ result.CopySuccess = resp.Header.Get("x-ms-copy-status")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/files/copy_abort.go b/storage/2018-03-28/file/files/copy_abort.go
new file mode 100644
index 0000000..2f09131
--- /dev/null
+++ b/storage/2018-03-28/file/files/copy_abort.go
@@ -0,0 +1,104 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// AbortCopy aborts a pending Copy File operation, and leaves a destination file with zero length and full metadata
+func (client Client) AbortCopy(ctx context.Context, accountName, shareName, path, fileName, copyID string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "AbortCopy", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "AbortCopy", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "AbortCopy", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "AbortCopy", "`fileName` cannot be an empty string.")
+ }
+ if copyID == "" {
+ return result, validation.NewError("files.Client", "AbortCopy", "`copyID` cannot be an empty string.")
+ }
+
+ req, err := client.AbortCopyPreparer(ctx, accountName, shareName, path, fileName, copyID)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "AbortCopy", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.AbortCopySender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "AbortCopy", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.AbortCopyResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "AbortCopy", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// AbortCopyPreparer prepares the AbortCopy request.
+func (client Client) AbortCopyPreparer(ctx context.Context, accountName, shareName, path, fileName, copyID string) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "copy"),
+ "copyid": autorest.Encode("query", copyID),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-copy-action": "abort",
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// AbortCopySender sends the AbortCopy request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) AbortCopySender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// AbortCopyResponder handles the response to the AbortCopy request. The method always
+// closes the http.Response Body.
+func (client Client) AbortCopyResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/files/copy_wait.go b/storage/2018-03-28/file/files/copy_wait.go
new file mode 100644
index 0000000..e6a646b
--- /dev/null
+++ b/storage/2018-03-28/file/files/copy_wait.go
@@ -0,0 +1,55 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+)
+
+type CopyAndWaitResult struct {
+ autorest.Response
+
+ CopyID string
+}
+
+const DefaultCopyPollDuration = 15 * time.Second
+
+// CopyAndWait is a convenience method which doesn't exist in the API, which copies the file and then waits for the copy to complete
+func (client Client) CopyAndWait(ctx context.Context, accountName, shareName, path, fileName string, input CopyInput, pollDuration time.Duration) (result CopyResult, err error) {
+ copy, e := client.Copy(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ result.Response = copy.Response
+ err = fmt.Errorf("Error copying: %s", e)
+ return
+ }
+
+ result.CopyID = copy.CopyID
+
+ // since the API doesn't return a LRO, this is a hack which also polls every 10s, but should be sufficient
+ for true {
+ props, e := client.GetProperties(ctx, accountName, shareName, path, fileName)
+ if e != nil {
+ result.Response = copy.Response
+ err = fmt.Errorf("Error waiting for copy: %s", e)
+ return
+ }
+
+ switch strings.ToLower(props.CopyStatus) {
+ case "pending":
+ time.Sleep(pollDuration)
+ continue
+
+ case "success":
+ return
+
+ default:
+ err = fmt.Errorf("Unexpected CopyState %q", e)
+ return
+ }
+ }
+
+ return
+}
diff --git a/storage/2018-03-28/file/files/copy_wait_test.go b/storage/2018-03-28/file/files/copy_wait_test.go
new file mode 100644
index 0000000..bac351c
--- /dev/null
+++ b/storage/2018-03-28/file/files/copy_wait_test.go
@@ -0,0 +1,129 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/file/shares"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestFilesCopyAndWaitFromURL(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := shares.NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ input := shares.CreateInput{
+ QuotaInGB: 10,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+ defer sharesClient.Delete(ctx, accountName, shareName, false)
+
+ filesClient := NewWithEnvironment(client.Environment)
+ filesClient.Client = client.PrepareWithAuthorizer(filesClient.Client, storageAuth)
+
+ copiedFileName := "ubuntu.iso"
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+
+ t.Logf("[DEBUG] Copy And Waiting..")
+ if _, err := filesClient.CopyAndWait(ctx, accountName, shareName, "", copiedFileName, copyInput, DefaultCopyPollDuration); err != nil {
+ t.Fatalf("Error copy & waiting: %s", err)
+ }
+
+ t.Logf("[DEBUG] Asserting that the file's ready..")
+
+ props, err := filesClient.GetProperties(ctx, accountName, shareName, "", copiedFileName)
+ if err != nil {
+ t.Fatalf("Error retrieving file: %s", err)
+ }
+
+ if !strings.EqualFold(props.CopyStatus, "success") {
+ t.Fatalf("Expected the Copy Status to be `Success` but got %q", props.CopyStatus)
+ }
+}
+
+func TestFilesCopyAndWaitFromBlob(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := shares.NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ input := shares.CreateInput{
+ QuotaInGB: 10,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+ defer sharesClient.Delete(ctx, accountName, shareName, false)
+
+ filesClient := NewWithEnvironment(client.Environment)
+ filesClient.Client = client.PrepareWithAuthorizer(filesClient.Client, storageAuth)
+
+ originalFileName := "ubuntu.iso"
+ copiedFileName := "ubuntu-copied.iso"
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+ t.Logf("[DEBUG] Copy And Waiting the original file..")
+ if _, err := filesClient.CopyAndWait(ctx, accountName, shareName, "", originalFileName, copyInput, DefaultCopyPollDuration); err != nil {
+ t.Fatalf("Error copy & waiting: %s", err)
+ }
+
+ t.Logf("[DEBUG] Now copying that blob..")
+ duplicateInput := CopyInput{
+ CopySource: fmt.Sprintf("%s/%s/%s", endpoints.GetFileEndpoint(filesClient.BaseURI, accountName), shareName, originalFileName),
+ }
+ if _, err := filesClient.CopyAndWait(ctx, accountName, shareName, "", copiedFileName, duplicateInput, DefaultCopyPollDuration); err != nil {
+ t.Fatalf("Error copying duplicate: %s", err)
+ }
+
+ t.Logf("[DEBUG] Asserting that the file's ready..")
+ props, err := filesClient.GetProperties(ctx, accountName, shareName, "", copiedFileName)
+ if err != nil {
+ t.Fatalf("Error retrieving file: %s", err)
+ }
+
+ if !strings.EqualFold(props.CopyStatus, "success") {
+ t.Fatalf("Expected the Copy Status to be `Success` but got %q", props.CopyStatus)
+ }
+}
diff --git a/storage/2018-03-28/file/files/create.go b/storage/2018-03-28/file/files/create.go
new file mode 100644
index 0000000..85d4b0b
--- /dev/null
+++ b/storage/2018-03-28/file/files/create.go
@@ -0,0 +1,146 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type CreateInput struct {
+ // This header specifies the maximum size for the file, up to 1 TiB.
+ ContentLength int64
+
+ // The MIME content type of the file
+ // If not specified, the default type is application/octet-stream.
+ ContentType *string
+
+ // Specifies which content encodings have been applied to the file.
+ // This value is returned to the client when the Get File operation is performed
+ // on the file resource and can be used to decode file content.
+ ContentEncoding *string
+
+ // Specifies the natural languages used by this resource.
+ ContentLanguage *string
+
+ // The File service stores this value but does not use or modify it.
+ CacheControl *string
+
+ // Sets the file's MD5 hash.
+ ContentMD5 *string
+
+ // Sets the file’s Content-Disposition header.
+ ContentDisposition *string
+
+ MetaData map[string]string
+}
+
+// Create creates a new file or replaces a file.
+func (client Client) Create(ctx context.Context, accountName, shareName, path, fileName string, input CreateInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "Create", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "Create", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "Create", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "Create", "`fileName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("files.Client", "Create", "`input.MetaData` cannot be an empty string.")
+ }
+
+ req, err := client.CreatePreparer(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "Create", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "Create", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "Create", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreatePreparer prepares the Create request.
+func (client Client) CreatePreparer(ctx context.Context, accountName, shareName, path, fileName string, input CreateInput) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-content-length": input.ContentLength,
+ "x-ms-type": "file",
+ }
+
+ if input.ContentDisposition != nil {
+ headers["x-ms-content-disposition"] = *input.ContentDisposition
+ }
+
+ if input.ContentEncoding != nil {
+ headers["x-ms-content-encoding"] = *input.ContentEncoding
+ }
+
+ if input.ContentMD5 != nil {
+ headers["x-ms-content-md5"] = *input.ContentMD5
+ }
+
+ if input.ContentType != nil {
+ headers["x-ms-content-type"] = *input.ContentType
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSender sends the Create request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateResponder handles the response to the Create request. The method always
+// closes the http.Response Body.
+func (client Client) CreateResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/files/delete.go b/storage/2018-03-28/file/files/delete.go
new file mode 100644
index 0000000..5debd76
--- /dev/null
+++ b/storage/2018-03-28/file/files/delete.go
@@ -0,0 +1,94 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete immediately deletes the file from the File Share.
+func (client Client) Delete(ctx context.Context, accountName, shareName, path, fileName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "Delete", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "Delete", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "Delete", "`fileName` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, shareName, path, fileName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, shareName, path, fileName string) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/files/lifecycle_test.go b/storage/2018-03-28/file/files/lifecycle_test.go
new file mode 100644
index 0000000..ec514a2
--- /dev/null
+++ b/storage/2018-03-28/file/files/lifecycle_test.go
@@ -0,0 +1,144 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/file/shares"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestFilesLifeCycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := shares.NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ input := shares.CreateInput{
+ QuotaInGB: 1,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+ defer sharesClient.Delete(ctx, accountName, shareName, false)
+
+ filesClient := NewWithEnvironment(client.Environment)
+ filesClient.Client = client.PrepareWithAuthorizer(filesClient.Client, storageAuth)
+
+ fileName := "bled5.png"
+ contentEncoding := "application/vnd+panda"
+
+ t.Logf("[DEBUG] Creating Top Level File..")
+ createInput := CreateInput{
+ ContentLength: 1024,
+ ContentEncoding: &contentEncoding,
+ }
+ if _, err := filesClient.Create(ctx, accountName, shareName, "", fileName, createInput); err != nil {
+ t.Fatalf("Error creating Top-Level File: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Properties for the Top-Level File..")
+ file, err := filesClient.GetProperties(ctx, accountName, shareName, "", fileName)
+ if err != nil {
+ t.Fatalf("Error retrieving Top-Level File: %s", err)
+ }
+
+ if *file.ContentLength != 1024 {
+ t.Fatalf("Expected the Content-Length to be 1024 but got %d", *file.ContentLength)
+ }
+
+ if file.ContentEncoding != contentEncoding {
+ t.Fatalf("Expected the Content-Encoding to be %q but got %q", contentEncoding, file.ContentEncoding)
+ }
+
+ updatedSize := int64(2048)
+ updatedEncoding := "application/vnd+pandas2"
+ updatedInput := SetPropertiesInput{
+ ContentEncoding: &updatedEncoding,
+ ContentLength: &updatedSize,
+ }
+ if _, err := filesClient.SetProperties(ctx, accountName, shareName, "", fileName, updatedInput); err != nil {
+ t.Fatalf("Error setting properties: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-retrieving Properties for the Top-Level File..")
+ file, err = filesClient.GetProperties(ctx, accountName, shareName, "", fileName)
+ if err != nil {
+ t.Fatalf("Error retrieving Top-Level File: %s", err)
+ }
+
+ if *file.ContentLength != 2048 {
+ t.Fatalf("Expected the Content-Length to be 1024 but got %d", *file.ContentLength)
+ }
+
+ if file.ContentEncoding != updatedEncoding {
+ t.Fatalf("Expected the Content-Encoding to be %q but got %q", updatedEncoding, file.ContentEncoding)
+ }
+
+ t.Logf("[DEBUG] Setting MetaData..")
+ metaData := map[string]string{
+ "hello": "there",
+ }
+ if _, err := filesClient.SetMetaData(ctx, accountName, shareName, "", fileName, metaData); err != nil {
+ t.Fatalf("Error setting MetaData: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving MetaData..")
+ retrievedMetaData, err := filesClient.GetMetaData(ctx, accountName, shareName, "", fileName)
+ if err != nil {
+ t.Fatalf("Error retrieving MetaData: %s", err)
+ }
+ if len(retrievedMetaData.MetaData) != 1 {
+ t.Fatalf("Expected 1 item but got %d", len(retrievedMetaData.MetaData))
+ }
+ if retrievedMetaData.MetaData["hello"] != "there" {
+ t.Fatalf("Expected `hello` to be `there` but got %q", retrievedMetaData.MetaData["hello"])
+ }
+
+ t.Logf("[DEBUG] Re-Setting MetaData..")
+ metaData = map[string]string{
+ "hello": "there",
+ "second": "thing",
+ }
+ if _, err := filesClient.SetMetaData(ctx, accountName, shareName, "", fileName, metaData); err != nil {
+ t.Fatalf("Error setting MetaData: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-Retrieving MetaData..")
+ retrievedMetaData, err = filesClient.GetMetaData(ctx, accountName, shareName, "", fileName)
+ if err != nil {
+ t.Fatalf("Error retrieving MetaData: %s", err)
+ }
+ if len(retrievedMetaData.MetaData) != 2 {
+ t.Fatalf("Expected 2 items but got %d", len(retrievedMetaData.MetaData))
+ }
+ if retrievedMetaData.MetaData["hello"] != "there" {
+ t.Fatalf("Expected `hello` to be `there` but got %q", retrievedMetaData.MetaData["hello"])
+ }
+ if retrievedMetaData.MetaData["second"] != "thing" {
+ t.Fatalf("Expected `second` to be `thing` but got %q", retrievedMetaData.MetaData["second"])
+ }
+
+ t.Logf("[DEBUG] Deleting Top Level File..")
+ if _, err := filesClient.Delete(ctx, accountName, shareName, "", fileName); err != nil {
+ t.Fatalf("Error deleting Top-Level File: %s", err)
+ }
+}
diff --git a/storage/2018-03-28/file/files/metadata_get.go b/storage/2018-03-28/file/files/metadata_get.go
new file mode 100644
index 0000000..fd62f90
--- /dev/null
+++ b/storage/2018-03-28/file/files/metadata_get.go
@@ -0,0 +1,111 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetMetaDataResult struct {
+ autorest.Response
+
+ MetaData map[string]string
+}
+
+// GetMetaData returns the MetaData for the specified File.
+func (client Client) GetMetaData(ctx context.Context, accountName, shareName, path, fileName string) (result GetMetaDataResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "GetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "GetMetaData", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "GetMetaData", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "GetMetaData", "`fileName` cannot be an empty string.")
+ }
+
+ req, err := client.GetMetaDataPreparer(ctx, accountName, shareName, path, fileName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "GetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetMetaDataSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "GetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "GetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetMetaDataPreparer prepares the GetMetaData request.
+func (client Client) GetMetaDataPreparer(ctx context.Context, accountName, shareName, path, fileName string) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetMetaDataSender sends the GetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetMetaDataResponder handles the response to the GetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) GetMetaDataResponder(resp *http.Response) (result GetMetaDataResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ //metadata.ByParsingFromHeaders(&result.MetaData),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/files/metadata_set.go b/storage/2018-03-28/file/files/metadata_set.go
new file mode 100644
index 0000000..41e3ffc
--- /dev/null
+++ b/storage/2018-03-28/file/files/metadata_set.go
@@ -0,0 +1,105 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// SetMetaData updates the specified File to have the specified MetaData.
+func (client Client) SetMetaData(ctx context.Context, accountName, shareName, path, fileName string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "SetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "SetMetaData", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "SetMetaData", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "SetMetaData", "`fileName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("files.Client", "SetMetaData", fmt.Sprintf("`metaData` is not valid: %s.", err))
+ }
+
+ req, err := client.SetMetaDataPreparer(ctx, accountName, shareName, path, fileName, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "SetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetMetaDataSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "SetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "SetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetMetaDataPreparer prepares the SetMetaData request.
+func (client Client) SetMetaDataPreparer(ctx context.Context, accountName, shareName, path, fileName string, metaData map[string]string) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetMetaDataSender sends the SetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetMetaDataResponder handles the response to the SetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) SetMetaDataResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/files/properties_get.go b/storage/2018-03-28/file/files/properties_get.go
new file mode 100644
index 0000000..c6a0c39
--- /dev/null
+++ b/storage/2018-03-28/file/files/properties_get.go
@@ -0,0 +1,144 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetResult struct {
+ autorest.Response
+
+ CacheControl string
+ ContentDisposition string
+ ContentEncoding string
+ ContentLanguage string
+ ContentLength *int64
+ ContentMD5 string
+ ContentType string
+ CopyID string
+ CopyStatus string
+ CopySource string
+ CopyProgress string
+ CopyStatusDescription string
+ CopyCompletionTime string
+ Encrypted bool
+
+ MetaData map[string]string
+}
+
+// GetProperties returns the Properties for the specified file
+func (client Client) GetProperties(ctx context.Context, accountName, shareName, path, fileName string) (result GetResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "GetProperties", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "GetProperties", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "GetProperties", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "GetProperties", "`fileName` cannot be an empty string.")
+ }
+
+ req, err := client.GetPropertiesPreparer(ctx, accountName, shareName, path, fileName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "GetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetPropertiesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "GetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "GetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPropertiesPreparer prepares the GetProperties request.
+func (client Client) GetPropertiesPreparer(ctx context.Context, accountName, shareName, path, fileName string) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsHead(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetPropertiesSender sends the GetProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetPropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetPropertiesResponder handles the response to the GetProperties request. The method always
+// closes the http.Response Body.
+func (client Client) GetPropertiesResponder(resp *http.Response) (result GetResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.CacheControl = resp.Header.Get("Cache-Control")
+ result.ContentDisposition = resp.Header.Get("Content-Disposition")
+ result.ContentEncoding = resp.Header.Get("Content-Encoding")
+ result.ContentLanguage = resp.Header.Get("Content-Language")
+ result.ContentMD5 = resp.Header.Get("x-ms-content-md5")
+ result.ContentType = resp.Header.Get("Content-Type")
+ result.CopyID = resp.Header.Get("x-ms-copy-id")
+ result.CopyProgress = resp.Header.Get("x-ms-copy-progress")
+ result.CopySource = resp.Header.Get("x-ms-copy-source")
+ result.CopyStatus = resp.Header.Get("x-ms-copy-status")
+ result.CopyStatusDescription = resp.Header.Get("x-ms-copy-status-description")
+ result.CopyCompletionTime = resp.Header.Get("x-ms-copy-completion-time")
+ result.Encrypted = strings.EqualFold(resp.Header.Get("x-ms-server-encrypted"), "true")
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+
+ contentLengthRaw := resp.Header.Get("Content-Length")
+ if contentLengthRaw != "" {
+ contentLength, err := strconv.Atoi(contentLengthRaw)
+ if err != nil {
+ return result, fmt.Errorf("Error parsing %q for Content-Length as an integer: %s", contentLengthRaw, err)
+ }
+ contentLengthI64 := int64(contentLength)
+ result.ContentLength = &contentLengthI64
+ }
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/files/properties_set.go b/storage/2018-03-28/file/files/properties_set.go
new file mode 100644
index 0000000..79fffc2
--- /dev/null
+++ b/storage/2018-03-28/file/files/properties_set.go
@@ -0,0 +1,160 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type SetPropertiesInput struct {
+ // Resizes a file to the specified size.
+ // If the specified byte value is less than the current size of the file,
+ // then all ranges above the specified byte value are cleared.
+ ContentLength *int64
+
+ // Modifies the cache control string for the file.
+ // If this property is not specified on the request, then the property will be cleared for the file.
+ // Subsequent calls to Get File Properties will not return this property,
+ // unless it is explicitly set on the file again.
+ ContentControl *string
+
+ // Sets the file’s Content-Disposition header.
+ // If this property is not specified on the request, then the property will be cleared for the file.
+ // Subsequent calls to Get File Properties will not return this property,
+ // unless it is explicitly set on the file again.
+ ContentDisposition *string
+
+ // Sets the file's content encoding.
+ // If this property is not specified on the request, then the property will be cleared for the file.
+ // Subsequent calls to Get File Properties will not return this property,
+ // unless it is explicitly set on the file again.
+ ContentEncoding *string
+
+ // Sets the file's content language.
+ // If this property is not specified on the request, then the property will be cleared for the file.
+ // Subsequent calls to Get File Properties will not return this property,
+ // unless it is explicitly set on the file again.
+ ContentLanguage *string
+
+ // Sets the file's MD5 hash.
+ // If this property is not specified on the request, then the property will be cleared for the file.
+ // Subsequent calls to Get File Properties will not return this property,
+ // unless it is explicitly set on the file again.
+ ContentMD5 *string
+
+ // Sets the file's content type.
+ // If this property is not specified on the request, then the property will be cleared for the file.
+ // Subsequent calls to Get File Properties will not return this property,
+ // unless it is explicitly set on the file again.
+ ContentType *string
+}
+
+// SetProperties sets the specified properties on the specified File
+func (client Client) SetProperties(ctx context.Context, accountName, shareName, path, fileName string, input SetPropertiesInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "SetProperties", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "SetProperties", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "SetProperties", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "SetProperties", "`fileName` cannot be an empty string.")
+ }
+
+ req, err := client.SetPropertiesPreparer(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "SetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetPropertiesSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "SetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "SetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetPropertiesPreparer prepares the SetProperties request.
+func (client Client) SetPropertiesPreparer(ctx context.Context, accountName, shareName, path, fileName string, input SetPropertiesInput) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-type": "file",
+ }
+
+ if input.ContentControl != nil {
+ headers["x-ms-cache-control"] = *input.ContentControl
+ }
+ if input.ContentDisposition != nil {
+ headers["x-ms-content-disposition"] = *input.ContentDisposition
+ }
+ if input.ContentEncoding != nil {
+ headers["x-ms-content-encoding"] = *input.ContentEncoding
+ }
+ if input.ContentLanguage != nil {
+ headers["x-ms-content-language"] = *input.ContentLanguage
+ }
+ if input.ContentLength != nil {
+ headers["x-ms-content-length"] = *input.ContentLength
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-content-md5"] = *input.ContentMD5
+ }
+ if input.ContentType != nil {
+ headers["x-ms-content-type"] = *input.ContentType
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetPropertiesSender sends the SetProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetPropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetPropertiesResponder handles the response to the SetProperties request. The method always
+// closes the http.Response Body.
+func (client Client) SetPropertiesResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/files/range_clear.go b/storage/2018-03-28/file/files/range_clear.go
new file mode 100644
index 0000000..5d8145f
--- /dev/null
+++ b/storage/2018-03-28/file/files/range_clear.go
@@ -0,0 +1,112 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type ClearByteRangeInput struct {
+ StartBytes int64
+ EndBytes int64
+}
+
+// ClearByteRange clears the specified Byte Range from within the specified File
+func (client Client) ClearByteRange(ctx context.Context, accountName, shareName, path, fileName string, input ClearByteRangeInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "ClearByteRange", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "ClearByteRange", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "ClearByteRange", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "ClearByteRange", "`fileName` cannot be an empty string.")
+ }
+ if input.StartBytes < 0 {
+ return result, validation.NewError("files.Client", "ClearByteRange", "`input.StartBytes` must be greater or equal to 0.")
+ }
+ if input.EndBytes <= 0 {
+ return result, validation.NewError("files.Client", "ClearByteRange", "`input.EndBytes` must be greater than 0.")
+ }
+
+ req, err := client.ClearByteRangePreparer(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "ClearByteRange", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ClearByteRangeSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "ClearByteRange", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ClearByteRangeResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "ClearByteRange", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ClearByteRangePreparer prepares the ClearByteRange request.
+func (client Client) ClearByteRangePreparer(ctx context.Context, accountName, shareName, path, fileName string, input ClearByteRangeInput) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "range"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-write": "clear",
+ "x-ms-range": fmt.Sprintf("bytes=%d-%d", input.StartBytes, input.EndBytes),
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ClearByteRangeSender sends the ClearByteRange request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ClearByteRangeSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ClearByteRangeResponder handles the response to the ClearByteRange request. The method always
+// closes the http.Response Body.
+func (client Client) ClearByteRangeResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/files/range_get.go b/storage/2018-03-28/file/files/range_get.go
new file mode 100644
index 0000000..733d3f5
--- /dev/null
+++ b/storage/2018-03-28/file/files/range_get.go
@@ -0,0 +1,121 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetByteRangeInput struct {
+ StartBytes int64
+ EndBytes int64
+}
+
+type GetByteRangeResult struct {
+ autorest.Response
+
+ Contents []byte
+}
+
+// GetByteRange returns the specified Byte Range from the specified File.
+func (client Client) GetByteRange(ctx context.Context, accountName, shareName, path, fileName string, input GetByteRangeInput) (result GetByteRangeResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "GetByteRange", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "GetByteRange", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "GetByteRange", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "GetByteRange", "`fileName` cannot be an empty string.")
+ }
+ if input.StartBytes < 0 {
+ return result, validation.NewError("files.Client", "GetByteRange", "`input.StartBytes` must be greater or equal to 0.")
+ }
+ if input.EndBytes <= 0 {
+ return result, validation.NewError("files.Client", "GetByteRange", "`input.EndBytes` must be greater than 0.")
+ }
+ expectedBytes := input.EndBytes - input.StartBytes
+ if expectedBytes < (4 * 1024) {
+ return result, validation.NewError("files.Client", "GetByteRange", "Requested Byte Range must be at least 4KB.")
+ }
+ if expectedBytes > (4 * 1024 * 1024) {
+ return result, validation.NewError("files.Client", "GetByteRange", "Requested Byte Range must be at most 4MB.")
+ }
+
+ req, err := client.GetByteRangePreparer(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "GetByteRange", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetByteRangeSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "GetByteRange", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetByteRangeResponder(resp, expectedBytes)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "GetByteRange", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetByteRangePreparer prepares the GetByteRange request.
+func (client Client) GetByteRangePreparer(ctx context.Context, accountName, shareName, path, fileName string, input GetByteRangeInput) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-range": fmt.Sprintf("bytes=%d-%d", input.StartBytes, input.EndBytes-1),
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetByteRangeSender sends the GetByteRange request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetByteRangeSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetByteRangeResponder handles the response to the GetByteRange request. The method always
+// closes the http.Response Body.
+func (client Client) GetByteRangeResponder(resp *http.Response, length int64) (result GetByteRangeResult, err error) {
+ result.Contents = make([]byte, length)
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusPartialContent),
+ autorest.ByUnmarshallingBytes(&result.Contents),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/files/range_get_file.go b/storage/2018-03-28/file/files/range_get_file.go
new file mode 100644
index 0000000..9e5be17
--- /dev/null
+++ b/storage/2018-03-28/file/files/range_get_file.go
@@ -0,0 +1,128 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "math"
+ "runtime"
+ "sync"
+
+ "github.com/Azure/go-autorest/autorest"
+)
+
+// GetFile is a helper method to download a file by chunking it automatically
+func (client Client) GetFile(ctx context.Context, accountName, shareName, path, fileName string, parallelism int) (result autorest.Response, outputBytes []byte, err error) {
+
+ // first look up the file and check out how many bytes it is
+ file, e := client.GetProperties(ctx, accountName, shareName, path, fileName)
+ if err != nil {
+ result = file.Response
+ err = e
+ return
+ }
+
+ if file.ContentLength == nil {
+ err = fmt.Errorf("Content-Length was nil!")
+ return
+ }
+
+ length := int64(*file.ContentLength)
+ chunkSize := int64(4 * 1024 * 1024) // 4MB
+
+ if chunkSize > length {
+ chunkSize = length
+ }
+
+ // then split that up into chunks and retrieve it retrieve it into the 'results' set
+ chunks := int(math.Ceil(float64(length) / float64(chunkSize)))
+ workerCount := parallelism * runtime.NumCPU()
+ if workerCount > chunks {
+ workerCount = chunks
+ }
+
+ var waitGroup sync.WaitGroup
+ waitGroup.Add(workerCount)
+
+ results := make([]*downloadFileChunkResult, chunks)
+ errors := make(chan error, chunkSize)
+
+ for i := 0; i < chunks; i++ {
+ go func(i int) {
+ log.Printf("[DEBUG] Downloading Chunk %d of %d", i+1, chunks)
+
+ dfci := downloadFileChunkInput{
+ thisChunk: i,
+ chunkSize: chunkSize,
+ fileSize: length,
+ }
+
+ result, err := client.downloadFileChunk(ctx, accountName, shareName, path, fileName, dfci)
+ if err != nil {
+ errors <- err
+ waitGroup.Done()
+ return
+ }
+
+ // if there's no error, we should have bytes, so this is safe
+ results[i] = result
+
+ waitGroup.Done()
+ }(i)
+ }
+ waitGroup.Wait()
+
+ // TODO: we should switch to hashicorp/multi-error here
+ if len(errors) > 0 {
+ err = fmt.Errorf("Error downloading file: %s", <-errors)
+ return
+ }
+
+ // then finally put it all together, in order and return it
+ output := make([]byte, length)
+ for _, v := range results {
+ copy(output[v.startBytes:v.endBytes], v.bytes)
+ }
+
+ outputBytes = output
+ return
+}
+
+type downloadFileChunkInput struct {
+ thisChunk int
+ chunkSize int64
+ fileSize int64
+}
+
+type downloadFileChunkResult struct {
+ startBytes int64
+ endBytes int64
+ bytes []byte
+}
+
+func (client Client) downloadFileChunk(ctx context.Context, accountName, shareName, path, fileName string, input downloadFileChunkInput) (*downloadFileChunkResult, error) {
+ startBytes := input.chunkSize * int64(input.thisChunk)
+ endBytes := startBytes + input.chunkSize
+
+ // the last chunk may exceed the size of the file
+ remaining := input.fileSize - startBytes
+ if input.chunkSize > remaining {
+ endBytes = startBytes + remaining
+ }
+
+ getInput := GetByteRangeInput{
+ StartBytes: startBytes,
+ EndBytes: endBytes,
+ }
+ result, err := client.GetByteRange(ctx, accountName, shareName, path, fileName, getInput)
+ if err != nil {
+ return nil, fmt.Errorf("Error putting bytes: %s", err)
+ }
+
+ output := downloadFileChunkResult{
+ startBytes: startBytes,
+ endBytes: endBytes,
+ bytes: result.Contents,
+ }
+ return &output, nil
+}
diff --git a/storage/2018-03-28/file/files/range_get_file_test.go b/storage/2018-03-28/file/files/range_get_file_test.go
new file mode 100644
index 0000000..b1d3c59
--- /dev/null
+++ b/storage/2018-03-28/file/files/range_get_file_test.go
@@ -0,0 +1,108 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/file/shares"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestGetSmallFile(t *testing.T) {
+ // the purpose of this test is to verify that the small, single-chunked file gets downloaded correctly
+ testGetFile(t, "small-file.png", "image/png")
+}
+
+func TestGetLargeFile(t *testing.T) {
+ // the purpose of this test is to verify that the large, multi-chunked file gets downloaded correctly
+ testGetFile(t, "blank-large-file.dmg", "application/x-apple-diskimage")
+}
+
+func testGetFile(t *testing.T, fileName string, contentType string) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := shares.NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ input := shares.CreateInput{
+ QuotaInGB: 10,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+ defer sharesClient.Delete(ctx, accountName, shareName, false)
+
+ filesClient := NewWithEnvironment(client.Environment)
+ filesClient.Client = client.PrepareWithAuthorizer(filesClient.Client, storageAuth)
+
+ // store files outside of this directory, since they're reused
+ file, err := os.Open("../../../testdata/" + fileName)
+ if err != nil {
+ t.Fatalf("Error opening: %s", err)
+ }
+
+ info, err := file.Stat()
+ if err != nil {
+ t.Fatalf("Error 'stat'-ing: %s", err)
+ }
+
+ t.Logf("[DEBUG] Creating Top Level File..")
+ createFileInput := CreateInput{
+ ContentLength: info.Size(),
+ ContentType: &contentType,
+ }
+ if _, err := filesClient.Create(ctx, accountName, shareName, "", fileName, createFileInput); err != nil {
+ t.Fatalf("Error creating Top-Level File: %s", err)
+ }
+
+ t.Logf("[DEBUG] Uploading File..")
+ if err := filesClient.PutFile(ctx, accountName, shareName, "", fileName, file, 4); err != nil {
+ t.Fatalf("Error uploading File: %s", err)
+ }
+
+ t.Logf("[DEBUG] Downloading file..")
+ _, downloadedBytes, err := filesClient.GetFile(ctx, accountName, shareName, "", fileName, 4)
+ if err != nil {
+ t.Fatalf("Error downloading file: %s", err)
+ }
+
+ t.Logf("[DEBUG] Asserting the files are the same size..")
+ expectedBytes := make([]byte, info.Size())
+ file.Read(expectedBytes)
+ if len(expectedBytes) != len(downloadedBytes) {
+ t.Fatalf("Expected %d bytes but got %d", len(expectedBytes), len(downloadedBytes))
+ }
+
+ t.Logf("[DEBUG] Asserting the files are the same content-wise..")
+ // overkill, but it's this or shasum-ing
+ for i := int64(0); i < info.Size(); i++ {
+ if expectedBytes[i] != downloadedBytes[i] {
+ t.Fatalf("Expected byte %d to be %q but got %q", i, expectedBytes[i], downloadedBytes[i])
+ }
+ }
+
+ t.Logf("[DEBUG] Deleting Top Level File..")
+ if _, err := filesClient.Delete(ctx, accountName, shareName, "", fileName); err != nil {
+ t.Fatalf("Error deleting Top-Level File: %s", err)
+ }
+
+}
diff --git a/storage/2018-03-28/file/files/range_put.go b/storage/2018-03-28/file/files/range_put.go
new file mode 100644
index 0000000..77fe101
--- /dev/null
+++ b/storage/2018-03-28/file/files/range_put.go
@@ -0,0 +1,129 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type PutByteRangeInput struct {
+ StartBytes int64
+ EndBytes int64
+
+ // Content is the File Contents for the specified range
+ // which can be at most 4MB
+ Content []byte
+}
+
+// PutByteRange puts the specified Byte Range in the specified File.
+func (client Client) PutByteRange(ctx context.Context, accountName, shareName, path, fileName string, input PutByteRangeInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "PutByteRange", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "PutByteRange", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "PutByteRange", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "PutByteRange", "`fileName` cannot be an empty string.")
+ }
+ if input.StartBytes < 0 {
+ return result, validation.NewError("files.Client", "PutByteRange", "`input.StartBytes` must be greater or equal to 0.")
+ }
+ if input.EndBytes <= 0 {
+ return result, validation.NewError("files.Client", "PutByteRange", "`input.EndBytes` must be greater than 0.")
+ }
+
+ expectedBytes := input.EndBytes - input.StartBytes
+ actualBytes := len(input.Content)
+ if expectedBytes != int64(actualBytes) {
+ return result, validation.NewError("files.Client", "PutByteRange", fmt.Sprintf("The specified byte-range (%d) didn't match the content size (%d).", expectedBytes, actualBytes))
+ }
+ if expectedBytes < (4 * 1024) {
+ return result, validation.NewError("files.Client", "PutByteRange", "Specified Byte Range must be at least 4KB.")
+ }
+
+ if expectedBytes > (4 * 1024 * 1024) {
+ return result, validation.NewError("files.Client", "PutByteRange", "Specified Byte Range must be at most 4MB.")
+ }
+
+ req, err := client.PutByteRangePreparer(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "PutByteRange", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutByteRangeSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "PutByteRange", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutByteRangeResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "PutByteRange", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutByteRangePreparer prepares the PutByteRange request.
+func (client Client) PutByteRangePreparer(ctx context.Context, accountName, shareName, path, fileName string, input PutByteRangeInput) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "range"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-write": "update",
+ "x-ms-range": fmt.Sprintf("bytes=%d-%d", input.StartBytes, input.EndBytes-1),
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithBytes(&input.Content))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutByteRangeSender sends the PutByteRange request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutByteRangeSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutByteRangeResponder handles the response to the PutByteRange request. The method always
+// closes the http.Response Body.
+func (client Client) PutByteRangeResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/files/range_put_file.go b/storage/2018-03-28/file/files/range_put_file.go
new file mode 100644
index 0000000..a39cd37
--- /dev/null
+++ b/storage/2018-03-28/file/files/range_put_file.go
@@ -0,0 +1,107 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "log"
+ "math"
+ "os"
+ "runtime"
+ "sync"
+
+ "github.com/Azure/go-autorest/autorest"
+)
+
+// PutFile is a helper method which takes a file, and automatically chunks it up, rather than having to do this yourself
+func (client Client) PutFile(ctx context.Context, accountName, shareName, path, fileName string, file *os.File, parallelism int) error {
+ fileInfo, err := file.Stat()
+ if err != nil {
+ return fmt.Errorf("Error loading file info: %s", err)
+ }
+
+ fileSize := fileInfo.Size()
+ chunkSize := 4 * 1024 * 1024 // 4MB
+ if chunkSize > int(fileSize) {
+ chunkSize = int(fileSize)
+ }
+ chunks := int(math.Ceil(float64(fileSize) / float64(chunkSize*1.0)))
+
+ workerCount := parallelism * runtime.NumCPU()
+ if workerCount > chunks {
+ workerCount = chunks
+ }
+
+ var waitGroup sync.WaitGroup
+ waitGroup.Add(workerCount)
+ errors := make(chan error, chunkSize)
+
+ for i := 0; i < chunks; i++ {
+ go func(i int) {
+ log.Printf("[DEBUG] Chunk %d of %d", i+1, chunks)
+
+ uci := uploadChunkInput{
+ thisChunk: i,
+ chunkSize: chunkSize,
+ fileSize: fileSize,
+ }
+
+ _, err := client.uploadChunk(ctx, accountName, shareName, path, fileName, uci, file)
+ if err != nil {
+ errors <- err
+ waitGroup.Done()
+ return
+ }
+
+ waitGroup.Done()
+ return
+ }(i)
+ }
+ waitGroup.Wait()
+
+ // TODO: we should switch to hashicorp/multi-error here
+ if len(errors) > 0 {
+ return fmt.Errorf("Error uploading file: %s", <-errors)
+ }
+
+ return nil
+}
+
+type uploadChunkInput struct {
+ thisChunk int
+ chunkSize int
+ fileSize int64
+}
+
+func (client Client) uploadChunk(ctx context.Context, accountName, shareName, path, fileName string, input uploadChunkInput, file *os.File) (result autorest.Response, err error) {
+ startBytes := int64(input.chunkSize * input.thisChunk)
+ endBytes := startBytes + int64(input.chunkSize)
+
+ // the last size may exceed the size of the file
+ remaining := input.fileSize - startBytes
+ if int64(input.chunkSize) > remaining {
+ endBytes = startBytes + remaining
+ }
+
+ bytesToRead := int(endBytes) - int(startBytes)
+ bytes := make([]byte, bytesToRead)
+
+ _, err = file.ReadAt(bytes, startBytes)
+ if err != nil {
+ if err != io.EOF {
+ return result, fmt.Errorf("Error reading bytes: %s", err)
+ }
+ }
+
+ putBytesInput := PutByteRangeInput{
+ StartBytes: startBytes,
+ EndBytes: endBytes,
+ Content: bytes,
+ }
+ result, err = client.PutByteRange(ctx, accountName, shareName, path, fileName, putBytesInput)
+ if err != nil {
+ return result, fmt.Errorf("Error putting bytes: %s", err)
+ }
+
+ return
+}
diff --git a/storage/2018-03-28/file/files/range_put_file_test.go b/storage/2018-03-28/file/files/range_put_file_test.go
new file mode 100644
index 0000000..cfc736f
--- /dev/null
+++ b/storage/2018-03-28/file/files/range_put_file_test.go
@@ -0,0 +1,86 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/file/shares"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestPutSmallFile(t *testing.T) {
+ // the purpose of this test is to ensure that a small file (< 4MB) is a single chunk
+ testPutFile(t, "small-file.png", "image/png")
+}
+
+func TestPutLargeFile(t *testing.T) {
+ // the purpose of this test is to ensure that large files (> 4MB) are chunked
+ testPutFile(t, "blank-large-file.dmg", "application/x-apple-diskimage")
+}
+
+func testPutFile(t *testing.T, fileName string, contentType string) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := shares.NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ input := shares.CreateInput{
+ QuotaInGB: 10,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+ defer sharesClient.Delete(ctx, accountName, shareName, false)
+
+ filesClient := NewWithEnvironment(client.Environment)
+ filesClient.Client = client.PrepareWithAuthorizer(filesClient.Client, storageAuth)
+
+ // store files outside of this directory, since they're reused
+ file, err := os.Open("../../../testdata/" + fileName)
+ if err != nil {
+ t.Fatalf("Error opening: %s", err)
+ }
+
+ info, err := file.Stat()
+ if err != nil {
+ t.Fatalf("Error 'stat'-ing: %s", err)
+ }
+
+ t.Logf("[DEBUG] Creating Top Level File..")
+ createFileInput := CreateInput{
+ ContentLength: info.Size(),
+ ContentType: &contentType,
+ }
+ if _, err := filesClient.Create(ctx, accountName, shareName, "", fileName, createFileInput); err != nil {
+ t.Fatalf("Error creating Top-Level File: %s", err)
+ }
+
+ t.Logf("[DEBUG] Uploading File..")
+ if err := filesClient.PutFile(ctx, accountName, shareName, "", fileName, file, 4); err != nil {
+ t.Fatalf("Error uploading File: %s", err)
+ }
+
+ t.Logf("[DEBUG] Deleting Top Level File..")
+ if _, err := filesClient.Delete(ctx, accountName, shareName, "", fileName); err != nil {
+ t.Fatalf("Error deleting Top-Level File: %s", err)
+ }
+}
diff --git a/storage/2018-03-28/file/files/ranges_list.go b/storage/2018-03-28/file/files/ranges_list.go
new file mode 100644
index 0000000..ea309f9
--- /dev/null
+++ b/storage/2018-03-28/file/files/ranges_list.go
@@ -0,0 +1,114 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type ListRangesResult struct {
+ autorest.Response
+
+ Ranges []Range `xml:"Range"`
+}
+
+type Range struct {
+ Start string `xml:"Start"`
+ End string `xml:"End"`
+}
+
+// ListRanges returns the list of valid ranges for the specified File.
+func (client Client) ListRanges(ctx context.Context, accountName, shareName, path, fileName string) (result ListRangesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "ListRanges", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "ListRanges", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "ListRanges", "`shareName` must be a lower-cased string.")
+ }
+ if path == "" {
+ return result, validation.NewError("files.Client", "ListRanges", "`path` cannot be an empty string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "ListRanges", "`fileName` cannot be an empty string.")
+ }
+
+ req, err := client.ListRangesPreparer(ctx, accountName, shareName, path, fileName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "ListRanges", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ListRangesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "ListRanges", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ListRangesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "ListRanges", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ListRangesPreparer prepares the ListRanges request.
+func (client Client) ListRangesPreparer(ctx context.Context, accountName, shareName, path, fileName string) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "rangelist"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ListRangesSender sends the ListRanges request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ListRangesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ListRangesResponder handles the response to the ListRanges request. The method always
+// closes the http.Response Body.
+func (client Client) ListRangesResponder(resp *http.Response) (result ListRangesResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/files/resource_id.go b/storage/2018-03-28/file/files/resource_id.go
new file mode 100644
index 0000000..ed1208d
--- /dev/null
+++ b/storage/2018-03-28/file/files/resource_id.go
@@ -0,0 +1,64 @@
+package files
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given File
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, shareName, directoryName, filePath string) string {
+ domain := endpoints.GetFileEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s/%s/%s", domain, shareName, directoryName, filePath)
+}
+
+type ResourceID struct {
+ AccountName string
+ DirectoryName string
+ FileName string
+ ShareName string
+}
+
+// ParseResourceID parses the specified Resource ID and returns an object
+// which can be used to interact with Files within a Storage Share.
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://account1.file.core.chinacloudapi.cn/share1/directory1/file1.txt
+ // example: https://account1.file.core.chinacloudapi.cn/share1/directory1/directory2/file1.txt
+
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ path := strings.TrimPrefix(uri.Path, "/")
+ segments := strings.Split(path, "/")
+ if len(segments) == 0 {
+ return nil, fmt.Errorf("Expected the path to contain segments but got none")
+ }
+
+ shareName := segments[0]
+ fileName := segments[len(segments)-1]
+
+ directoryName := strings.TrimPrefix(path, shareName)
+ directoryName = strings.TrimPrefix(directoryName, "/")
+ directoryName = strings.TrimSuffix(directoryName, fileName)
+ directoryName = strings.TrimSuffix(directoryName, "/")
+ return &ResourceID{
+ AccountName: *accountName,
+ ShareName: shareName,
+ DirectoryName: directoryName,
+ FileName: fileName,
+ }, nil
+}
diff --git a/storage/2018-03-28/file/files/resource_id_test.go b/storage/2018-03-28/file/files/resource_id_test.go
new file mode 100644
index 0000000..1b521ac
--- /dev/null
+++ b/storage/2018-03-28/file/files/resource_id_test.go
@@ -0,0 +1,131 @@
+package files
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.file.core.chinacloudapi.cn/share1/directory1/file1.txt",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.file.core.cloudapi.de/share1/directory1/file1.txt",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.file.core.windows.net/share1/directory1/file1.txt",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.file.core.usgovcloudapi.net/share1/directory1/file1.txt",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "share1", "directory1", "file1.txt")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.file.core.chinacloudapi.cn/share1/directory1/file1.txt",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.file.core.cloudapi.de/share1/directory1/file1.txt",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.file.core.windows.net/share1/directory1/file1.txt",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.file.core.usgovcloudapi.net/share1/directory1/file1.txt",
+ },
+ }
+
+ t.Logf("[DEBUG] Top Level Files")
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.ShareName != "share1" {
+ t.Fatalf("Expected Share Name to be `share1` but got %q", actual.ShareName)
+ }
+ if actual.DirectoryName != "directory1" {
+ t.Fatalf("Expected Directory Name to be `directory1` but got %q", actual.DirectoryName)
+ }
+ if actual.FileName != "file1.txt" {
+ t.Fatalf("Expected File Name to be `file1.txt` but got %q", actual.FileName)
+ }
+ }
+
+ testData = []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.file.core.chinacloudapi.cn/share1/directory1/directory2/file1.txt",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.file.core.cloudapi.de/share1/directory1/directory2/file1.txt",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.file.core.windows.net/share1/directory1/directory2/file1.txt",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.file.core.usgovcloudapi.net/share1/directory1/directory2/file1.txt",
+ },
+ }
+
+ t.Logf("[DEBUG] Nested Files")
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.ShareName != "share1" {
+ t.Fatalf("Expected Share Name to be `share1` but got %q", actual.ShareName)
+ }
+ if actual.DirectoryName != "directory1/directory2" {
+ t.Fatalf("Expected Directory Name to be `directory1/directory2` but got %q", actual.DirectoryName)
+ }
+ if actual.FileName != "file1.txt" {
+ t.Fatalf("Expected File Name to be `file1.txt` but got %q", actual.FileName)
+ }
+ }
+}
diff --git a/storage/2018-03-28/file/files/version.go b/storage/2018-03-28/file/files/version.go
new file mode 100644
index 0000000..0f66afa
--- /dev/null
+++ b/storage/2018-03-28/file/files/version.go
@@ -0,0 +1,14 @@
+package files
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2018-03-28"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2018-03-28/file/shares/README.md b/storage/2018-03-28/file/shares/README.md
new file mode 100644
index 0000000..cba86e8
--- /dev/null
+++ b/storage/2018-03-28/file/shares/README.md
@@ -0,0 +1,43 @@
+## File Storage Shares SDK for API version 2018-03-28
+
+This package allows you to interact with the Shares File Storage API
+
+### Supported Authorizers
+
+* Azure Active Directory (for the Resource Endpoint `https://storage.azure.com`)
+* SharedKeyLite (Blob, File & Queue)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/file/shares"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ shareName := "myshare"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ sharesClient := shares.New()
+ sharesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ input := shares.CreateInput{
+ QuotaInGB: 2,
+ }
+ if _, err := sharesClient.Create(ctx, accountName, shareName, input); err != nil {
+ return fmt.Errorf("Error creating Share: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2018-03-28/file/shares/acl_get.go b/storage/2018-03-28/file/shares/acl_get.go
new file mode 100644
index 0000000..ea6ff4c
--- /dev/null
+++ b/storage/2018-03-28/file/shares/acl_get.go
@@ -0,0 +1,98 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetACLResult struct {
+ autorest.Response
+
+ SignedIdentifiers []SignedIdentifier `xml:"SignedIdentifier"`
+}
+
+// GetACL get the Access Control List for the specified Storage Share
+func (client Client) GetACL(ctx context.Context, accountName, shareName string) (result GetACLResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "GetACL", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "GetACL", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "GetACL", "`shareName` must be a lower-cased string.")
+ }
+
+ req, err := client.GetACLPreparer(ctx, accountName, shareName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetACL", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetACLSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetACL", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetACLResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetACL", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetACLPreparer prepares the GetACL request.
+func (client Client) GetACLPreparer(ctx context.Context, accountName, shareName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "comp": autorest.Encode("query", "acl"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetACLSender sends the GetACL request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetACLSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetACLResponder handles the response to the GetACL request. The method always
+// closes the http.Response Body.
+func (client Client) GetACLResponder(resp *http.Response) (result GetACLResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/shares/acl_set.go b/storage/2018-03-28/file/shares/acl_set.go
new file mode 100644
index 0000000..18d1788
--- /dev/null
+++ b/storage/2018-03-28/file/shares/acl_set.go
@@ -0,0 +1,103 @@
+package shares
+
+import (
+ "context"
+ "encoding/xml"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type setAcl struct {
+ SignedIdentifiers []SignedIdentifier `xml:"SignedIdentifier"`
+
+ XMLName xml.Name `xml:"SignedIdentifiers"`
+}
+
+// SetACL sets the specified Access Control List on the specified Storage Share
+func (client Client) SetACL(ctx context.Context, accountName, shareName string, acls []SignedIdentifier) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "SetACL", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "SetACL", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "SetACL", "`shareName` must be a lower-cased string.")
+ }
+
+ req, err := client.SetACLPreparer(ctx, accountName, shareName, acls)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetACL", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetACLSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetACL", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetACLResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetACL", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetACLPreparer prepares the SetACL request.
+func (client Client) SetACLPreparer(ctx context.Context, accountName, shareName string, acls []SignedIdentifier) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "comp": autorest.Encode("query", "acl"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ input := setAcl{
+ SignedIdentifiers: acls,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithXML(&input))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetACLSender sends the SetACL request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetACLSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetACLResponder handles the response to the SetACL request. The method always
+// closes the http.Response Body.
+func (client Client) SetACLResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/shares/client.go b/storage/2018-03-28/file/shares/client.go
new file mode 100644
index 0000000..4f3a6f9
--- /dev/null
+++ b/storage/2018-03-28/file/shares/client.go
@@ -0,0 +1,25 @@
+package shares
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for File Storage Shares.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2018-03-28/file/shares/create.go b/storage/2018-03-28/file/shares/create.go
new file mode 100644
index 0000000..84fd40d
--- /dev/null
+++ b/storage/2018-03-28/file/shares/create.go
@@ -0,0 +1,109 @@
+package shares
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type CreateInput struct {
+ // Specifies the maximum size of the share, in gigabytes.
+ // Must be greater than 0, and less than or equal to 5TB (5120).
+ QuotaInGB int
+
+ MetaData map[string]string
+}
+
+// Create creates the specified Storage Share within the specified Storage Account
+func (client Client) Create(ctx context.Context, accountName, shareName string, input CreateInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "Create", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "Create", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "Create", "`shareName` must be a lower-cased string.")
+ }
+ if input.QuotaInGB <= 0 || input.QuotaInGB > 5120 {
+ return result, validation.NewError("shares.Client", "Create", "`input.QuotaInGB` must be greater than 0, and less than/equal to 5TB (5120 GB)")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("shares.Client", "Create", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.CreatePreparer(ctx, accountName, shareName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "Create", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "Create", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "Create", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreatePreparer prepares the Create request.
+func (client Client) CreatePreparer(ctx context.Context, accountName, shareName string, input CreateInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "share"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-share-quota": input.QuotaInGB,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSender sends the Create request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateResponder handles the response to the Create request. The method always
+// closes the http.Response Body.
+func (client Client) CreateResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/shares/delete.go b/storage/2018-03-28/file/shares/delete.go
new file mode 100644
index 0000000..70ef985
--- /dev/null
+++ b/storage/2018-03-28/file/shares/delete.go
@@ -0,0 +1,94 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete deletes the specified Storage Share from within a Storage Account
+func (client Client) Delete(ctx context.Context, accountName, shareName string, deleteSnapshots bool) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "Delete", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "Delete", "`shareName` must be a lower-cased string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, shareName, deleteSnapshots)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, shareName string, deleteSnapshots bool) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "share"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if deleteSnapshots {
+ headers["x-ms-delete-snapshots"] = "include"
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/shares/lifecycle_test.go b/storage/2018-03-28/file/shares/lifecycle_test.go
new file mode 100644
index 0000000..fbab96d
--- /dev/null
+++ b/storage/2018-03-28/file/shares/lifecycle_test.go
@@ -0,0 +1,152 @@
+package shares
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestSharesLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ input := CreateInput{
+ QuotaInGB: 1,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+
+ snapshot, err := sharesClient.CreateSnapshot(ctx, accountName, shareName, CreateSnapshotInput{})
+ if err != nil {
+ t.Fatalf("Error taking snapshot: %s", err)
+ }
+ t.Logf("Snapshot Date Time: %s", snapshot.SnapshotDateTime)
+
+ snapshotDetails, err := sharesClient.GetSnapshot(ctx, accountName, shareName, snapshot.SnapshotDateTime)
+ if err != nil {
+ t.Fatalf("Error retrieving snapshot: %s", err)
+ }
+
+ t.Logf("MetaData: %s", snapshotDetails.MetaData)
+
+ _, err = sharesClient.DeleteSnapshot(ctx, accountName, shareName, snapshot.SnapshotDateTime)
+ if err != nil {
+ t.Fatalf("Error deleting snapshot: %s", err)
+ }
+
+ stats, err := sharesClient.GetStats(ctx, accountName, shareName)
+ if err != nil {
+ t.Fatalf("Error retrieving stats: %s", err)
+ }
+
+ if stats.ShareUsageBytes != 0 {
+ t.Fatalf("Expected `stats.ShareUsageBytes` to be 0 but got: %d", stats.ShareUsageBytes)
+ }
+
+ share, err := sharesClient.GetProperties(ctx, accountName, shareName)
+ if err != nil {
+ t.Fatalf("Error retrieving share: %s", err)
+ }
+ if share.ShareQuota != 1 {
+ t.Fatalf("Expected Quota to be 1 but got: %d", share.ShareQuota)
+ }
+
+ _, err = sharesClient.SetProperties(ctx, accountName, shareName, 5)
+ if err != nil {
+ t.Fatalf("Error updating quota: %s", err)
+ }
+
+ share, err = sharesClient.GetProperties(ctx, accountName, shareName)
+ if err != nil {
+ t.Fatalf("Error retrieving share: %s", err)
+ }
+ if share.ShareQuota != 5 {
+ t.Fatalf("Expected Quota to be 5 but got: %d", share.ShareQuota)
+ }
+
+ updatedMetaData := map[string]string{
+ "hello": "world",
+ }
+ _, err = sharesClient.SetMetaData(ctx, accountName, shareName, updatedMetaData)
+ if err != nil {
+ t.Fatalf("Erorr setting metadata: %s", err)
+ }
+
+ result, err := sharesClient.GetMetaData(ctx, accountName, shareName)
+ if err != nil {
+ t.Fatalf("Error retrieving metadata: %s", err)
+ }
+
+ if result.MetaData["hello"] != "world" {
+ t.Fatalf("Expected metadata `hello` to be `world` but got: %q", result.MetaData["hello"])
+ }
+ if len(result.MetaData) != 1 {
+ t.Fatalf("Expected metadata to be 1 item but got: %s", result.MetaData)
+ }
+
+ acls, err := sharesClient.GetACL(ctx, accountName, shareName)
+ if err != nil {
+ t.Fatalf("Error retrieving ACL's: %s", err)
+ }
+ if len(acls.SignedIdentifiers) != 0 {
+ t.Fatalf("Expected 0 identifiers but got %d", len(acls.SignedIdentifiers))
+ }
+
+ updatedAcls := []SignedIdentifier{
+ {
+ Id: "abc123",
+ AccessPolicy: AccessPolicy{
+ Start: "2020-07-01T08:49:37.0000000Z",
+ Expiry: "2020-07-01T09:49:37.0000000Z",
+ Permission: "rwd",
+ },
+ },
+ {
+ Id: "bcd234",
+ AccessPolicy: AccessPolicy{
+ Start: "2020-07-01T08:49:37.0000000Z",
+ Expiry: "2020-07-01T09:49:37.0000000Z",
+ Permission: "rwd",
+ },
+ },
+ }
+ _, err = sharesClient.SetACL(ctx, accountName, shareName, updatedAcls)
+ if err != nil {
+ t.Fatalf("Error setting ACL's: %s", err)
+ }
+
+ acls, err = sharesClient.GetACL(ctx, accountName, shareName)
+ if err != nil {
+ t.Fatalf("Error retrieving ACL's: %s", err)
+ }
+ if len(acls.SignedIdentifiers) != 2 {
+ t.Fatalf("Expected 2 identifiers but got %d", len(acls.SignedIdentifiers))
+ }
+
+ _, err = sharesClient.Delete(ctx, accountName, shareName, false)
+ if err != nil {
+ t.Fatalf("Error deleting Share: %s", err)
+ }
+}
diff --git a/storage/2018-03-28/file/shares/metadata_get.go b/storage/2018-03-28/file/shares/metadata_get.go
new file mode 100644
index 0000000..9fa4d9f
--- /dev/null
+++ b/storage/2018-03-28/file/shares/metadata_get.go
@@ -0,0 +1,102 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetMetaDataResult struct {
+ autorest.Response
+
+ MetaData map[string]string
+}
+
+// GetMetaData returns the MetaData associated with the specified Storage Share
+func (client Client) GetMetaData(ctx context.Context, accountName, shareName string) (result GetMetaDataResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "GetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "GetMetaData", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "GetMetaData", "`shareName` must be a lower-cased string.")
+ }
+
+ req, err := client.GetMetaDataPreparer(ctx, accountName, shareName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetMetaDataSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetMetaDataPreparer prepares the GetMetaData request.
+func (client Client) GetMetaDataPreparer(ctx context.Context, accountName, shareName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetMetaDataSender sends the GetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetMetaDataResponder handles the response to the GetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) GetMetaDataResponder(resp *http.Response) (result GetMetaDataResult, err error) {
+ if resp.Header != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/shares/metadata_set.go b/storage/2018-03-28/file/shares/metadata_set.go
new file mode 100644
index 0000000..7e64e60
--- /dev/null
+++ b/storage/2018-03-28/file/shares/metadata_set.go
@@ -0,0 +1,97 @@
+package shares
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// SetMetaData sets the MetaData on the specified Storage Share
+func (client Client) SetMetaData(ctx context.Context, accountName, shareName string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "SetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "SetMetaData", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "SetMetaData", "`shareName` must be a lower-cased string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("shares.Client", "SetMetaData", fmt.Sprintf("`metadata` is not valid: %s.", err))
+ }
+
+ req, err := client.SetMetaDataPreparer(ctx, accountName, shareName, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetMetaDataSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetMetaDataPreparer prepares the SetMetaData request.
+func (client Client) SetMetaDataPreparer(ctx context.Context, accountName, shareName string, metaData map[string]string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetPropertiesSetMetaDataSender sends the SetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetMetaDataResponder handles the response to the SetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) SetMetaDataResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/shares/models.go b/storage/2018-03-28/file/shares/models.go
new file mode 100644
index 0000000..31ef7c2
--- /dev/null
+++ b/storage/2018-03-28/file/shares/models.go
@@ -0,0 +1,12 @@
+package shares
+
+type SignedIdentifier struct {
+ Id string `xml:"Id"`
+ AccessPolicy AccessPolicy `xml:"AccessPolicy"`
+}
+
+type AccessPolicy struct {
+ Start string `xml:"Start"`
+ Expiry string `xml:"Expiry"`
+ Permission string `xml:"Permission"`
+}
diff --git a/storage/2018-03-28/file/shares/properties_get.go b/storage/2018-03-28/file/shares/properties_get.go
new file mode 100644
index 0000000..80e26a4
--- /dev/null
+++ b/storage/2018-03-28/file/shares/properties_get.go
@@ -0,0 +1,111 @@
+package shares
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetPropertiesResult struct {
+ autorest.Response
+
+ MetaData map[string]string
+ ShareQuota int
+}
+
+// GetProperties returns the properties about the specified Storage Share
+func (client Client) GetProperties(ctx context.Context, accountName, shareName string) (result GetPropertiesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "GetProperties", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "GetProperties", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "GetProperties", "`shareName` must be a lower-cased string.")
+ }
+
+ req, err := client.GetPropertiesPreparer(ctx, accountName, shareName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetPropertiesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPropertiesPreparer prepares the GetProperties request.
+func (client Client) GetPropertiesPreparer(ctx context.Context, accountName, shareName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetPropertiesSender sends the GetProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetPropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetPropertiesResponder handles the response to the GetProperties request. The method always
+// closes the http.Response Body.
+func (client Client) GetPropertiesResponder(resp *http.Response) (result GetPropertiesResult, err error) {
+ if resp.Header != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+
+ quotaRaw := resp.Header.Get("x-ms-share-quota")
+ quota, e := strconv.Atoi(quotaRaw)
+ if e != nil {
+ return result, fmt.Errorf("Error converting %q to an integer: %s", quotaRaw, err)
+ }
+ result.ShareQuota = quota
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/shares/properties_set.go b/storage/2018-03-28/file/shares/properties_set.go
new file mode 100644
index 0000000..4553e5e
--- /dev/null
+++ b/storage/2018-03-28/file/shares/properties_set.go
@@ -0,0 +1,95 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// SetProperties lets you update the Quota for the specified Storage Share
+func (client Client) SetProperties(ctx context.Context, accountName, shareName string, newQuotaGB int) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "SetProperties", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "SetProperties", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "SetProperties", "`shareName` must be a lower-cased string.")
+ }
+ if newQuotaGB <= 0 || newQuotaGB > 5120 {
+ return result, validation.NewError("shares.Client", "SetProperties", "`newQuotaGB` must be greater than 0, and less than/equal to 5TB (5120 GB)")
+ }
+
+ req, err := client.SetPropertiesPreparer(ctx, accountName, shareName, newQuotaGB)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetPropertiesSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetPropertiesPreparer prepares the SetProperties request.
+func (client Client) SetPropertiesPreparer(ctx context.Context, accountName, shareName string, quotaGB int) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "comp": autorest.Encode("query", "properties"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-share-quota": quotaGB,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetPropertiesSender sends the SetProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetPropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetPropertiesResponder handles the response to the SetProperties request. The method always
+// closes the http.Response Body.
+func (client Client) SetPropertiesResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/shares/resource_id.go b/storage/2018-03-28/file/shares/resource_id.go
new file mode 100644
index 0000000..bfdcbfd
--- /dev/null
+++ b/storage/2018-03-28/file/shares/resource_id.go
@@ -0,0 +1,46 @@
+package shares
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given File Share
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, shareName string) string {
+ domain := endpoints.GetFileEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s", domain, shareName)
+}
+
+type ResourceID struct {
+ AccountName string
+ ShareName string
+}
+
+// ParseResourceID parses the specified Resource ID and returns an object
+// which can be used to interact with the Storage Shares SDK
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://foo.file.core.windows.net/Bar
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ shareName := strings.TrimPrefix(uri.Path, "/")
+ return &ResourceID{
+ AccountName: *accountName,
+ ShareName: shareName,
+ }, nil
+}
diff --git a/storage/2018-03-28/file/shares/resource_id_test.go b/storage/2018-03-28/file/shares/resource_id_test.go
new file mode 100644
index 0000000..1b7eea3
--- /dev/null
+++ b/storage/2018-03-28/file/shares/resource_id_test.go
@@ -0,0 +1,79 @@
+package shares
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.file.core.chinacloudapi.cn/share1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.file.core.cloudapi.de/share1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.file.core.windows.net/share1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.file.core.usgovcloudapi.net/share1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "share1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.file.core.chinacloudapi.cn/share1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.file.core.cloudapi.de/share1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.file.core.windows.net/share1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.file.core.usgovcloudapi.net/share1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected the account name to be `account1` but got %q", actual.AccountName)
+ }
+
+ if actual.ShareName != "share1" {
+ t.Fatalf("Expected the share name to be `share1` but got %q", actual.ShareName)
+ }
+ }
+}
diff --git a/storage/2018-03-28/file/shares/snapshot_create.go b/storage/2018-03-28/file/shares/snapshot_create.go
new file mode 100644
index 0000000..0ded38b
--- /dev/null
+++ b/storage/2018-03-28/file/shares/snapshot_create.go
@@ -0,0 +1,115 @@
+package shares
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type CreateSnapshotInput struct {
+ MetaData map[string]string
+}
+
+type CreateSnapshotResult struct {
+ autorest.Response
+
+ // This header is a DateTime value that uniquely identifies the share snapshot.
+ // The value of this header may be used in subsequent requests to access the share snapshot.
+ // This value is opaque.
+ SnapshotDateTime string
+}
+
+// CreateSnapshot creates a read-only snapshot of the share
+// A share can support creation of 200 share snapshots. Attempting to create more than 200 share snapshots fails with 409 (Conflict).
+// Attempting to create a share snapshot while a previous Snapshot Share operation is in progress fails with 409 (Conflict).
+func (client Client) CreateSnapshot(ctx context.Context, accountName, shareName string, input CreateSnapshotInput) (result CreateSnapshotResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "CreateSnapshot", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "CreateSnapshot", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "CreateSnapshot", "`shareName` must be a lower-cased string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("shares.Client", "CreateSnapshot", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.CreateSnapshotPreparer(ctx, accountName, shareName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "CreateSnapshot", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSnapshotSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "CreateSnapshot", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateSnapshotResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "CreateSnapshot", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreateSnapshotPreparer prepares the CreateSnapshot request.
+func (client Client) CreateSnapshotPreparer(ctx context.Context, accountName, shareName string, input CreateSnapshotInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "snapshot"),
+ "restype": autorest.Encode("query", "share"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSnapshotSender sends the CreateSnapshot request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSnapshotSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateSnapshotResponder handles the response to the CreateSnapshot request. The method always
+// closes the http.Response Body.
+func (client Client) CreateSnapshotResponder(resp *http.Response) (result CreateSnapshotResult, err error) {
+ result.SnapshotDateTime = resp.Header.Get("x-ms-snapshot")
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/shares/snapshot_delete.go b/storage/2018-03-28/file/shares/snapshot_delete.go
new file mode 100644
index 0000000..1f5d665
--- /dev/null
+++ b/storage/2018-03-28/file/shares/snapshot_delete.go
@@ -0,0 +1,94 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// DeleteSnapshot deletes the specified Snapshot of a Storage Share
+func (client Client) DeleteSnapshot(ctx context.Context, accountName, shareName string, shareSnapshot string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "DeleteSnapshot", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "DeleteSnapshot", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "DeleteSnapshot", "`shareName` must be a lower-cased string.")
+ }
+ if shareSnapshot == "" {
+ return result, validation.NewError("shares.Client", "DeleteSnapshot", "`shareSnapshot` cannot be an empty string.")
+ }
+
+ req, err := client.DeleteSnapshotPreparer(ctx, accountName, shareName, shareSnapshot)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "DeleteSnapshot", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSnapshotSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "DeleteSnapshot", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteSnapshotResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "DeleteSnapshot", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeleteSnapshotPreparer prepares the DeleteSnapshot request.
+func (client Client) DeleteSnapshotPreparer(ctx context.Context, accountName, shareName string, shareSnapshot string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "share"),
+ "sharesnapshot": autorest.Encode("query", shareSnapshot),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSnapshotSender sends the DeleteSnapshot request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSnapshotSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteSnapshotResponder handles the response to the DeleteSnapshot request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteSnapshotResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/shares/snapshot_get.go b/storage/2018-03-28/file/shares/snapshot_get.go
new file mode 100644
index 0000000..2cf5f16
--- /dev/null
+++ b/storage/2018-03-28/file/shares/snapshot_get.go
@@ -0,0 +1,105 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetSnapshotPropertiesResult struct {
+ autorest.Response
+
+ MetaData map[string]string
+}
+
+// GetSnapshot gets information about the specified Snapshot of the specified Storage Share
+func (client Client) GetSnapshot(ctx context.Context, accountName, shareName, snapshotShare string) (result GetSnapshotPropertiesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "GetSnapshot", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "GetSnapshot", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "GetSnapshot", "`shareName` must be a lower-cased string.")
+ }
+ if snapshotShare == "" {
+ return result, validation.NewError("shares.Client", "GetSnapshot", "`snapshotShare` cannot be an empty string.")
+ }
+
+ req, err := client.GetSnapshotPreparer(ctx, accountName, shareName, snapshotShare)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetSnapshot", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetSnapshotSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetSnapshot", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetSnapshotResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetSnapshot", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetSnapshotPreparer prepares the GetSnapshot request.
+func (client Client) GetSnapshotPreparer(ctx context.Context, accountName, shareName, snapshotShare string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "snapshot": autorest.Encode("query", snapshotShare),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetSnapshotSender sends the GetSnapshot request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetSnapshotSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetSnapshotResponder handles the response to the GetSnapshot request. The method always
+// closes the http.Response Body.
+func (client Client) GetSnapshotResponder(resp *http.Response) (result GetSnapshotPropertiesResult, err error) {
+ if resp.Header != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/shares/stats.go b/storage/2018-03-28/file/shares/stats.go
new file mode 100644
index 0000000..3539ecc
--- /dev/null
+++ b/storage/2018-03-28/file/shares/stats.go
@@ -0,0 +1,100 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetStatsResult struct {
+ autorest.Response
+
+ // The approximate size of the data stored on the share.
+ // Note that this value may not include all recently created or recently resized files.
+ ShareUsageBytes int64 `xml:"ShareUsageBytes"`
+}
+
+// GetStats returns information about the specified Storage Share
+func (client Client) GetStats(ctx context.Context, accountName, shareName string) (result GetStatsResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "GetStats", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "GetStats", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "GetStats", "`shareName` must be a lower-cased string.")
+ }
+
+ req, err := client.GetStatsPreparer(ctx, accountName, shareName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetStats", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetStatsSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetStats", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetStatsResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetStats", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetStatsPreparer prepares the GetStats request.
+func (client Client) GetStatsPreparer(ctx context.Context, accountName, shareName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "comp": autorest.Encode("query", "stats"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetStatsSender sends the GetStats request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetStatsSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetStatsResponder handles the response to the GetStats request. The method always
+// closes the http.Response Body.
+func (client Client) GetStatsResponder(resp *http.Response) (result GetStatsResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/file/shares/version.go b/storage/2018-03-28/file/shares/version.go
new file mode 100644
index 0000000..88cc6c4
--- /dev/null
+++ b/storage/2018-03-28/file/shares/version.go
@@ -0,0 +1,14 @@
+package shares
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2018-03-28"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2018-03-28/queue/messages/README.md b/storage/2018-03-28/queue/messages/README.md
new file mode 100644
index 0000000..635cbb8
--- /dev/null
+++ b/storage/2018-03-28/queue/messages/README.md
@@ -0,0 +1,43 @@
+## Queue Storage Messages SDK for API version 2018-03-28
+
+This package allows you to interact with the Messages Queue Storage API
+
+### Supported Authorizers
+
+* Azure Active Directory (for the Resource Endpoint `https://storage.azure.com`)
+* SharedKeyLite (Blob, File & Queue)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/queue/messages"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ queueName := "myqueue"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ messagesClient := messages.New()
+ messagesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ input := messages.PutInput{
+ Message: "hello",
+ }
+ if _, err := messagesClient.Put(ctx, accountName, queueName, input); err != nil {
+ return fmt.Errorf("Error creating Message: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2018-03-28/queue/messages/client.go b/storage/2018-03-28/queue/messages/client.go
new file mode 100644
index 0000000..08b1801
--- /dev/null
+++ b/storage/2018-03-28/queue/messages/client.go
@@ -0,0 +1,25 @@
+package messages
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for Messages.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2018-03-28/queue/messages/delete.go b/storage/2018-03-28/queue/messages/delete.go
new file mode 100644
index 0000000..1ec0e1a
--- /dev/null
+++ b/storage/2018-03-28/queue/messages/delete.go
@@ -0,0 +1,97 @@
+package messages
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete deletes a specific message
+func (client Client) Delete(ctx context.Context, accountName, queueName, messageID, popReceipt string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("messages.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("messages.Client", "Delete", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("messages.Client", "Delete", "`queueName` must be a lower-cased string.")
+ }
+ if messageID == "" {
+ return result, validation.NewError("messages.Client", "Delete", "`messageID` cannot be an empty string.")
+ }
+ if popReceipt == "" {
+ return result, validation.NewError("messages.Client", "Delete", "`popReceipt` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, queueName, messageID, popReceipt)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "messages.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, queueName, messageID, popReceipt string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ "messageID": autorest.Encode("path", messageID),
+ }
+
+ queryParameters := map[string]interface{}{
+ "popreceipt": autorest.Encode("query", popReceipt),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}/messages/{messageID}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/queue/messages/get.go b/storage/2018-03-28/queue/messages/get.go
new file mode 100644
index 0000000..4edeb6d
--- /dev/null
+++ b/storage/2018-03-28/queue/messages/get.go
@@ -0,0 +1,112 @@
+package messages
+
+import (
+ "context"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetInput struct {
+ // VisibilityTimeout specifies the new visibility timeout value, in seconds, relative to server time.
+ // The new value must be larger than or equal to 0, and cannot be larger than 7 days.
+ VisibilityTimeout *int
+}
+
+// Get retrieves one or more messages from the front of the queue
+func (client Client) Get(ctx context.Context, accountName, queueName string, numberOfMessages int, input GetInput) (result QueueMessagesListResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("messages.Client", "Get", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("messages.Client", "Get", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("messages.Client", "Get", "`queueName` must be a lower-cased string.")
+ }
+ if numberOfMessages < 1 || numberOfMessages > 32 {
+ return result, validation.NewError("messages.Client", "Get", "`numberOfMessages` must be between 1 and 32.")
+ }
+ if input.VisibilityTimeout != nil {
+ t := *input.VisibilityTimeout
+ maxTime := (time.Hour * 24 * 7).Seconds()
+ if t < 1 || t < int(maxTime) {
+ return result, validation.NewError("messages.Client", "Get", "`input.VisibilityTimeout` must be larger than or equal to 1 second, and cannot be larger than 7 days.")
+ }
+ }
+
+ req, err := client.GetPreparer(ctx, accountName, queueName, numberOfMessages, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Get", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "messages.Client", "Get", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Get", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPreparer prepares the Get request.
+func (client Client) GetPreparer(ctx context.Context, accountName, queueName string, numberOfMessages int, input GetInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "numofmessages": autorest.Encode("query", numberOfMessages),
+ }
+
+ if input.VisibilityTimeout != nil {
+ queryParameters["visibilitytimeout"] = autorest.Encode("query", *input.VisibilityTimeout)
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}/messages", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetSender sends the Get request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetResponder handles the response to the Get request. The method always
+// closes the http.Response Body.
+func (client Client) GetResponder(resp *http.Response) (result QueueMessagesListResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ autorest.ByUnmarshallingXML(&result),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/queue/messages/lifecycle_test.go b/storage/2018-03-28/queue/messages/lifecycle_test.go
new file mode 100644
index 0000000..6d13ae0
--- /dev/null
+++ b/storage/2018-03-28/queue/messages/lifecycle_test.go
@@ -0,0 +1,95 @@
+package messages
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/queue/queues"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestLifeCycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ queueName := fmt.Sprintf("queue-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ queuesClient := queues.NewWithEnvironment(client.Environment)
+ queuesClient.Client = client.PrepareWithStorageResourceManagerAuth(queuesClient.Client)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ messagesClient := NewWithEnvironment(client.Environment)
+ messagesClient.Client = client.PrepareWithAuthorizer(messagesClient.Client, storageAuth)
+
+ _, err = queuesClient.Create(ctx, accountName, queueName, map[string]string{})
+ if err != nil {
+ t.Fatalf("Error creating queue: %s", err)
+ }
+ defer queuesClient.Delete(ctx, accountName, queueName)
+
+ input := PutInput{
+ Message: "ohhai",
+ }
+ putResp, err := messagesClient.Put(ctx, accountName, queueName, input)
+ if err != nil {
+ t.Fatalf("Error putting message in queue: %s", err)
+ }
+
+ messageId := (*putResp.QueueMessages)[0].MessageId
+ popReceipt := (*putResp.QueueMessages)[0].PopReceipt
+
+ _, err = messagesClient.Update(ctx, accountName, queueName, messageId, UpdateInput{
+ PopReceipt: popReceipt,
+ Message: "Updated message",
+ VisibilityTimeout: 65,
+ })
+ if err != nil {
+ t.Fatalf("Error updating: %s", err)
+ }
+
+ for i := 0; i < 5; i++ {
+ input := PutInput{
+ Message: fmt.Sprintf("Message %d", i),
+ }
+ _, err := messagesClient.Put(ctx, accountName, queueName, input)
+ if err != nil {
+ t.Fatalf("Error putting message %d in queue: %s", i, err)
+ }
+ }
+
+ peakedMessages, err := messagesClient.Peek(ctx, accountName, queueName, 3)
+ if err != nil {
+ t.Fatalf("Error peaking messages: %s", err)
+ }
+
+ for _, v := range *peakedMessages.QueueMessages {
+ t.Logf("Message: %q", v.MessageId)
+ }
+
+ retrievedMessages, err := messagesClient.Get(ctx, accountName, queueName, 6, GetInput{})
+ if err != nil {
+ t.Fatalf("Error retrieving messages: %s", err)
+ }
+
+ for _, v := range *retrievedMessages.QueueMessages {
+ t.Logf("Message: %q", v.MessageId)
+
+ _, err = messagesClient.Delete(ctx, accountName, queueName, v.MessageId, v.PopReceipt)
+ if err != nil {
+ t.Fatalf("Error deleting message from queue: %s", err)
+ }
+ }
+}
diff --git a/storage/2018-03-28/queue/messages/models.go b/storage/2018-03-28/queue/messages/models.go
new file mode 100644
index 0000000..67815a8
--- /dev/null
+++ b/storage/2018-03-28/queue/messages/models.go
@@ -0,0 +1,21 @@
+package messages
+
+import "github.com/Azure/go-autorest/autorest"
+
+type QueueMessage struct {
+ MessageText string `xml:"MessageText"`
+}
+
+type QueueMessagesListResult struct {
+ autorest.Response
+
+ QueueMessages *[]QueueMessageResponse `xml:"QueueMessage"`
+}
+
+type QueueMessageResponse struct {
+ MessageId string `xml:"MessageId"`
+ InsertionTime string `xml:"InsertionTime"`
+ ExpirationTime string `xml:"ExpirationTime"`
+ PopReceipt string `xml:"PopReceipt"`
+ TimeNextVisible string `xml:"TimeNextVisible"`
+}
diff --git a/storage/2018-03-28/queue/messages/peek.go b/storage/2018-03-28/queue/messages/peek.go
new file mode 100644
index 0000000..7288bd5
--- /dev/null
+++ b/storage/2018-03-28/queue/messages/peek.go
@@ -0,0 +1,95 @@
+package messages
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Peek retrieves one or more messages from the front of the queue, but doesn't alter the visibility of the messages
+func (client Client) Peek(ctx context.Context, accountName, queueName string, numberOfMessages int) (result QueueMessagesListResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("messages.Client", "Peek", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("messages.Client", "Peek", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("messages.Client", "Peek", "`queueName` must be a lower-cased string.")
+ }
+ if numberOfMessages < 1 || numberOfMessages > 32 {
+ return result, validation.NewError("messages.Client", "Peek", "`numberOfMessages` must be between 1 and 32.")
+ }
+
+ req, err := client.PeekPreparer(ctx, accountName, queueName, numberOfMessages)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Peek", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PeekSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "messages.Client", "Peek", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PeekResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Peek", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PeekPreparer prepares the Peek request.
+func (client Client) PeekPreparer(ctx context.Context, accountName, queueName string, numberOfMessages int) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "numofmessages": autorest.Encode("query", numberOfMessages),
+ "peekonly": autorest.Encode("query", true),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}/messages", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PeekSender sends the Peek request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PeekSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PeekResponder handles the response to the Peek request. The method always
+// closes the http.Response Body.
+func (client Client) PeekResponder(resp *http.Response) (result QueueMessagesListResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ autorest.ByUnmarshallingXML(&result),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/queue/messages/put.go b/storage/2018-03-28/queue/messages/put.go
new file mode 100644
index 0000000..612b4a1
--- /dev/null
+++ b/storage/2018-03-28/queue/messages/put.go
@@ -0,0 +1,120 @@
+package messages
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type PutInput struct {
+ // A message must be in a format that can be included in an XML request with UTF-8 encoding.
+ // The encoded message can be up to 64 KB in size.
+ Message string
+
+ // The maximum time-to-live can be any positive number,
+ // as well as -1 indicating that the message does not expire.
+ // If this parameter is omitted, the default time-to-live is 7 days.
+ MessageTtl *int
+
+ // Specifies the new visibility timeout value, in seconds, relative to server time.
+ // The new value must be larger than or equal to 0, and cannot be larger than 7 days.
+ // The visibility timeout of a message cannot be set to a value later than the expiry time.
+ // visibilitytimeout should be set to a value smaller than the time-to-live value.
+ // If not specified, the default value is 0.
+ VisibilityTimeout *int
+}
+
+// Put adds a new message to the back of the message queue
+func (client Client) Put(ctx context.Context, accountName, queueName string, input PutInput) (result QueueMessagesListResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("messages.Client", "Put", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("messages.Client", "Put", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("messages.Client", "Put", "`queueName` must be a lower-cased string.")
+ }
+
+ req, err := client.PutPreparer(ctx, accountName, queueName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Put", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "messages.Client", "Put", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Put", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutPreparer prepares the Put request.
+func (client Client) PutPreparer(ctx context.Context, accountName, queueName string, input PutInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ queryParameters := map[string]interface{}{}
+
+ if input.MessageTtl != nil {
+ queryParameters["messagettl"] = autorest.Encode("path", *input.MessageTtl)
+ }
+
+ if input.VisibilityTimeout != nil {
+ queryParameters["visibilitytimeout"] = autorest.Encode("path", *input.VisibilityTimeout)
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ body := QueueMessage{
+ MessageText: input.Message,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPost(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}/messages", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithXML(body),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutSender sends the Put request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutResponder handles the response to the Put request. The method always
+// closes the http.Response Body.
+func (client Client) PutResponder(resp *http.Response) (result QueueMessagesListResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ autorest.ByUnmarshallingXML(&result),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/queue/messages/resource_id.go b/storage/2018-03-28/queue/messages/resource_id.go
new file mode 100644
index 0000000..7ece98a
--- /dev/null
+++ b/storage/2018-03-28/queue/messages/resource_id.go
@@ -0,0 +1,56 @@
+package messages
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Message within a Queue
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, queueName, messageID string) string {
+ domain := endpoints.GetQueueEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s/messages/%s", domain, queueName, messageID)
+}
+
+type ResourceID struct {
+ AccountName string
+ QueueName string
+ MessageID string
+}
+
+// ParseResourceID parses the specified Resource ID and returns an object
+// which can be used to interact with the Message within a Queue
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://account1.queue.core.chinacloudapi.cn/queue1/messages/message1
+
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ path := strings.TrimPrefix(uri.Path, "/")
+ segments := strings.Split(path, "/")
+ if len(segments) != 3 {
+ return nil, fmt.Errorf("Expected the path to contain 3 segments but got %d", len(segments))
+ }
+
+ queueName := segments[0]
+ messageID := segments[2]
+ return &ResourceID{
+ AccountName: *accountName,
+ MessageID: messageID,
+ QueueName: queueName,
+ }, nil
+}
diff --git a/storage/2018-03-28/queue/messages/resource_id_test.go b/storage/2018-03-28/queue/messages/resource_id_test.go
new file mode 100644
index 0000000..5053279
--- /dev/null
+++ b/storage/2018-03-28/queue/messages/resource_id_test.go
@@ -0,0 +1,81 @@
+package messages
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.queue.core.chinacloudapi.cn/queue1/messages/message1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.queue.core.cloudapi.de/queue1/messages/message1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.queue.core.windows.net/queue1/messages/message1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.queue.core.usgovcloudapi.net/queue1/messages/message1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "queue1", "message1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.queue.core.chinacloudapi.cn/queue1/messages/message1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.queue.core.cloudapi.de/queue1/messages/message1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.queue.core.windows.net/queue1/messages/message1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.queue.core.usgovcloudapi.net/queue1/messages/message1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.QueueName != "queue1" {
+ t.Fatalf("Expected Queue Name to be `queue1` but got %q", actual.QueueName)
+ }
+ if actual.MessageID != "message1" {
+ t.Fatalf("Expected Message ID to be `message1` but got %q", actual.MessageID)
+ }
+ }
+}
diff --git a/storage/2018-03-28/queue/messages/update.go b/storage/2018-03-28/queue/messages/update.go
new file mode 100644
index 0000000..fb10fad
--- /dev/null
+++ b/storage/2018-03-28/queue/messages/update.go
@@ -0,0 +1,115 @@
+package messages
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type UpdateInput struct {
+ // A message must be in a format that can be included in an XML request with UTF-8 encoding.
+ // The encoded message can be up to 64 KB in size.
+ Message string
+
+ // Specifies the valid pop receipt value required to modify this message.
+ PopReceipt string
+
+ // Specifies the new visibility timeout value, in seconds, relative to server time.
+ // The new value must be larger than or equal to 0, and cannot be larger than 7 days.
+ // The visibility timeout of a message cannot be set to a value later than the expiry time.
+ // A message can be updated until it has been deleted or has expired.
+ VisibilityTimeout int
+}
+
+// Update updates an existing message based on it's Pop Receipt
+func (client Client) Update(ctx context.Context, accountName, queueName string, messageID string, input UpdateInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("messages.Client", "Update", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("messages.Client", "Update", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("messages.Client", "Update", "`queueName` must be a lower-cased string.")
+ }
+ if input.PopReceipt == "" {
+ return result, validation.NewError("messages.Client", "Update", "`input.PopReceipt` cannot be an empty string.")
+ }
+
+ req, err := client.UpdatePreparer(ctx, accountName, queueName, messageID, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Update", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.UpdateSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "messages.Client", "Update", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.UpdateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Update", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// UpdatePreparer prepares the Update request.
+func (client Client) UpdatePreparer(ctx context.Context, accountName, queueName string, messageID string, input UpdateInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ "messageID": autorest.Encode("path", messageID),
+ }
+
+ queryParameters := map[string]interface{}{
+ "popreceipt": autorest.Encode("query", input.PopReceipt),
+ "visibilitytimeout": autorest.Encode("query", input.VisibilityTimeout),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ body := QueueMessage{
+ MessageText: input.Message,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}/messages/{messageID}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithXML(body),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// UpdateSender sends the Update request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) UpdateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// UpdateResponder handles the response to the Update request. The method always
+// closes the http.Response Body.
+func (client Client) UpdateResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/queue/messages/version.go b/storage/2018-03-28/queue/messages/version.go
new file mode 100644
index 0000000..4717045
--- /dev/null
+++ b/storage/2018-03-28/queue/messages/version.go
@@ -0,0 +1,14 @@
+package messages
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2018-03-28"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2018-03-28/queue/queues/README.md b/storage/2018-03-28/queue/queues/README.md
new file mode 100644
index 0000000..2d62998
--- /dev/null
+++ b/storage/2018-03-28/queue/queues/README.md
@@ -0,0 +1,43 @@
+## Queue Storage Queues SDK for API version 2018-03-28
+
+This package allows you to interact with the Queues Queue Storage API
+
+### Supported Authorizers
+
+* Azure Active Directory (for the Resource Endpoint `https://storage.azure.com`)
+* SharedKeyLite (Blob, File & Queue)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/queue/queues"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ queueName := "myqueue"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ queuesClient := queues.New()
+ queuesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ metadata := map[string]string{
+ "hello": "world",
+ }
+ if _, err := queuesClient.Create(ctx, accountName, queueName, metadata); err != nil {
+ return fmt.Errorf("Error creating Queue: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2018-03-28/queue/queues/client.go b/storage/2018-03-28/queue/queues/client.go
new file mode 100644
index 0000000..2f80085
--- /dev/null
+++ b/storage/2018-03-28/queue/queues/client.go
@@ -0,0 +1,25 @@
+package queues
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for Queue Storage Shares.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2018-03-28/queue/queues/create.go b/storage/2018-03-28/queue/queues/create.go
new file mode 100644
index 0000000..f18910a
--- /dev/null
+++ b/storage/2018-03-28/queue/queues/create.go
@@ -0,0 +1,92 @@
+package queues
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// Create creates the specified Queue within the specified Storage Account
+func (client Client) Create(ctx context.Context, accountName, queueName string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("queues.Client", "Create", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("queues.Client", "Create", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("queues.Client", "Create", "`queueName` must be a lower-cased string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("queues.Client", "Create", fmt.Sprintf("`metadata` is not valid: %s.", err))
+ }
+
+ req, err := client.CreatePreparer(ctx, accountName, queueName, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "Create", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "queues.Client", "Create", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "Create", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreatePreparer prepares the Create request.
+func (client Client) CreatePreparer(ctx context.Context, accountName string, queueName string, metaData map[string]string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSender sends the Create request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateResponder handles the response to the Create request. The method always
+// closes the http.Response Body.
+func (client Client) CreateResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/queue/queues/delete.go b/storage/2018-03-28/queue/queues/delete.go
new file mode 100644
index 0000000..5f70595
--- /dev/null
+++ b/storage/2018-03-28/queue/queues/delete.go
@@ -0,0 +1,85 @@
+package queues
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete deletes the specified Queue within the specified Storage Account
+func (client Client) Delete(ctx context.Context, accountName, queueName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("queues.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("queues.Client", "Delete", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("queues.Client", "Delete", "`queueName` must be a lower-cased string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, queueName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "queues.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName string, queueName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/queue/queues/lifecycle_test.go b/storage/2018-03-28/queue/queues/lifecycle_test.go
new file mode 100644
index 0000000..ff720f6
--- /dev/null
+++ b/storage/2018-03-28/queue/queues/lifecycle_test.go
@@ -0,0 +1,155 @@
+package queues
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestQueuesLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ queueName := fmt.Sprintf("queue-%d", testhelpers.RandomInt())
+
+ _, err = client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ queuesClient := NewWithEnvironment(client.Environment)
+ queuesClient.Client = client.PrepareWithStorageResourceManagerAuth(queuesClient.Client)
+
+ // first let's test an empty container
+ _, err = queuesClient.Create(ctx, accountName, queueName, map[string]string{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+
+ // then let's retrieve it to ensure there's no metadata..
+ resp, err := queuesClient.GetMetaData(ctx, accountName, queueName)
+ if err != nil {
+ t.Fatalf("Error retrieving MetaData: %s", err)
+ }
+ if len(resp.MetaData) != 0 {
+ t.Fatalf("Expected no MetaData but got: %s", err)
+ }
+
+ // then let's add some..
+ updatedMetaData := map[string]string{
+ "band": "panic",
+ "boots": "the-overpass",
+ }
+ _, err = queuesClient.SetMetaData(ctx, accountName, queueName, updatedMetaData)
+ if err != nil {
+ t.Fatalf("Error setting MetaData: %s", err)
+ }
+
+ resp, err = queuesClient.GetMetaData(ctx, accountName, queueName)
+ if err != nil {
+ t.Fatalf("Error re-retrieving MetaData: %s", err)
+ }
+
+ if len(resp.MetaData) != 2 {
+ t.Fatalf("Expected metadata to have 2 items but got: %s", resp.MetaData)
+ }
+ if resp.MetaData["band"] != "panic" {
+ t.Fatalf("Expected `band` to be `panic` but got: %s", resp.MetaData["band"])
+ }
+ if resp.MetaData["boots"] != "the-overpass" {
+ t.Fatalf("Expected `boots` to be `the-overpass` but got: %s", resp.MetaData["boots"])
+ }
+
+ // and woo let's remove it again
+ _, err = queuesClient.SetMetaData(ctx, accountName, queueName, map[string]string{})
+ if err != nil {
+ t.Fatalf("Error setting MetaData: %s", err)
+ }
+
+ resp, err = queuesClient.GetMetaData(ctx, accountName, queueName)
+ if err != nil {
+ t.Fatalf("Error retrieving MetaData: %s", err)
+ }
+ if len(resp.MetaData) != 0 {
+ t.Fatalf("Expected no MetaData but got: %s", err)
+ }
+
+ // set some properties
+ props := StorageServiceProperties{
+ Logging: &LoggingConfig{
+ Version: "1.0",
+ Delete: true,
+ Read: true,
+ Write: true,
+ RetentionPolicy: RetentionPolicy{
+ Enabled: true,
+ Days: 7,
+ },
+ },
+ Cors: &Cors{
+ CorsRule: CorsRule{
+ AllowedMethods: "GET,PUT",
+ AllowedOrigins: "http://www.example.com",
+ ExposedHeaders: "x-tempo-*",
+ AllowedHeaders: "x-tempo-*",
+ MaxAgeInSeconds: 500,
+ },
+ },
+ HourMetrics: &MetricsConfig{
+ Version: "1.0",
+ Enabled: false,
+ RetentionPolicy: RetentionPolicy{
+ Enabled: true,
+ Days: 7,
+ },
+ },
+ MinuteMetrics: &MetricsConfig{
+ Version: "1.0",
+ Enabled: false,
+ RetentionPolicy: RetentionPolicy{
+ Enabled: true,
+ Days: 7,
+ },
+ },
+ }
+ _, err = queuesClient.SetServiceProperties(ctx, accountName, props)
+ if err != nil {
+ t.Fatalf("SetServiceProperties failed: %s", err)
+ }
+
+ properties, err := queuesClient.GetServiceProperties(ctx, accountName)
+ if err != nil {
+ t.Fatalf("GetServiceProperties failed: %s", err)
+ }
+
+ if properties.Cors.CorsRule.AllowedMethods != "GET,PUT" {
+ t.Fatalf("CORS Methods weren't set!")
+ }
+
+ if properties.HourMetrics.Enabled {
+ t.Fatalf("HourMetrics were enabled when they shouldn't be!")
+ }
+
+ if properties.MinuteMetrics.Enabled {
+ t.Fatalf("MinuteMetrics were enabled when they shouldn't be!")
+ }
+
+ if !properties.Logging.Write {
+ t.Fatalf("Logging Write's was not enabled when they should be!")
+ }
+
+ log.Printf("[DEBUG] Deleting..")
+ _, err = queuesClient.Delete(ctx, accountName, queueName)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error deleting: %s", err))
+ }
+}
diff --git a/storage/2018-03-28/queue/queues/metadata_get.go b/storage/2018-03-28/queue/queues/metadata_get.go
new file mode 100644
index 0000000..9c230b6
--- /dev/null
+++ b/storage/2018-03-28/queue/queues/metadata_get.go
@@ -0,0 +1,101 @@
+package queues
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetMetaDataResult struct {
+ autorest.Response
+
+ MetaData map[string]string
+}
+
+// GetMetaData returns the metadata for this Queue
+func (client Client) GetMetaData(ctx context.Context, accountName, queueName string) (result GetMetaDataResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("queues.Client", "GetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("queues.Client", "GetMetaData", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("queues.Client", "GetMetaData", "`queueName` must be a lower-cased string.")
+ }
+
+ req, err := client.GetMetaDataPreparer(ctx, accountName, queueName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "GetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetMetaDataSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "queues.Client", "GetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "GetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetMetaDataPreparer prepares the GetMetaData request.
+func (client Client) GetMetaDataPreparer(ctx context.Context, accountName, queueName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetMetaDataSender sends the GetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetMetaDataResponder handles the response to the GetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) GetMetaDataResponder(resp *http.Response) (result GetMetaDataResult, err error) {
+ if resp != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/queue/queues/metadata_set.go b/storage/2018-03-28/queue/queues/metadata_set.go
new file mode 100644
index 0000000..51154a5
--- /dev/null
+++ b/storage/2018-03-28/queue/queues/metadata_set.go
@@ -0,0 +1,97 @@
+package queues
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// SetMetaData returns the metadata for this Queue
+func (client Client) SetMetaData(ctx context.Context, accountName, queueName string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("queues.Client", "SetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("queues.Client", "SetMetaData", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("queues.Client", "SetMetaData", "`queueName` must be a lower-cased string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("queues.Client", "SetMetaData", fmt.Sprintf("`metadata` is not valid: %s.", err))
+ }
+
+ req, err := client.SetMetaDataPreparer(ctx, accountName, queueName, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetMetaDataSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetMetaDataPreparer prepares the SetMetaData request.
+func (client Client) SetMetaDataPreparer(ctx context.Context, accountName, queueName string, metaData map[string]string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetMetaDataSender sends the SetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetMetaDataResponder handles the response to the SetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) SetMetaDataResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/queue/queues/models.go b/storage/2018-03-28/queue/queues/models.go
new file mode 100644
index 0000000..89c2380
--- /dev/null
+++ b/storage/2018-03-28/queue/queues/models.go
@@ -0,0 +1,42 @@
+package queues
+
+type StorageServiceProperties struct {
+ Logging *LoggingConfig `xml:"Logging,omitempty"`
+ HourMetrics *MetricsConfig `xml:"HourMetrics,omitempty"`
+ MinuteMetrics *MetricsConfig `xml:"MinuteMetrics,omitempty"`
+ Cors *Cors `xml:"Cors,omitempty"`
+}
+
+type LoggingConfig struct {
+ Version string `xml:"Version"`
+ Delete bool `xml:"Delete"`
+ Read bool `xml:"Read"`
+ Write bool `xml:"Write"`
+ RetentionPolicy RetentionPolicy `xml:"RetentionPolicy"`
+}
+
+type MetricsConfig struct {
+ Version string `xml:"Version"`
+ Enabled bool `xml:"Enabled"`
+ RetentionPolicy RetentionPolicy `xml:"RetentionPolicy"`
+
+ // Element IncludeAPIs is only expected when Metrics is enabled
+ IncludeAPIs *bool `xml:"IncludeAPIs,omitempty"`
+}
+
+type RetentionPolicy struct {
+ Enabled bool `xml:"Enabled"`
+ Days int `xml:"Days"`
+}
+
+type Cors struct {
+ CorsRule CorsRule `xml:"CorsRule"`
+}
+
+type CorsRule struct {
+ AllowedOrigins string `xml:"AllowedOrigins"`
+ AllowedMethods string `xml:"AllowedMethods"`
+ AllowedHeaders string `xml:"AllowedHeaders`
+ ExposedHeaders string `xml:"ExposedHeaders"`
+ MaxAgeInSeconds int `xml:"MaxAgeInSeconds"`
+}
diff --git a/storage/2018-03-28/queue/queues/properties_get.go b/storage/2018-03-28/queue/queues/properties_get.go
new file mode 100644
index 0000000..9d17fb2
--- /dev/null
+++ b/storage/2018-03-28/queue/queues/properties_get.go
@@ -0,0 +1,85 @@
+package queues
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type StorageServicePropertiesResponse struct {
+ StorageServiceProperties
+ autorest.Response
+}
+
+// SetServiceProperties gets the properties for this queue
+func (client Client) GetServiceProperties(ctx context.Context, accountName string) (result StorageServicePropertiesResponse, err error) {
+ if accountName == "" {
+ return result, validation.NewError("queues.Client", "SetServiceProperties", "`accountName` cannot be an empty string.")
+ }
+
+ req, err := client.GetServicePropertiesPreparer(ctx, accountName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetServiceProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetServicePropertiesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetServiceProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetServicePropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetServiceProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetServicePropertiesPreparer prepares the GetServiceProperties request.
+func (client Client) GetServicePropertiesPreparer(ctx context.Context, accountName string) (*http.Request, error) {
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "properties"),
+ "restype": autorest.Encode("path", "service"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetServicePropertiesSender sends the GetServiceProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetServicePropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetServicePropertiesResponder handles the response to the GetServiceProperties request. The method always
+// closes the http.Response Body.
+func (client Client) GetServicePropertiesResponder(resp *http.Response) (result StorageServicePropertiesResponse, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/queue/queues/properties_set.go b/storage/2018-03-28/queue/queues/properties_set.go
new file mode 100644
index 0000000..d6f6392
--- /dev/null
+++ b/storage/2018-03-28/queue/queues/properties_set.go
@@ -0,0 +1,80 @@
+package queues
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// SetServiceProperties sets the properties for this queue
+func (client Client) SetServiceProperties(ctx context.Context, accountName string, properties StorageServiceProperties) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("queues.Client", "SetServiceProperties", "`accountName` cannot be an empty string.")
+ }
+
+ req, err := client.SetServicePropertiesPreparer(ctx, accountName, properties)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetServiceProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetServicePropertiesSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetServiceProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetServicePropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetServiceProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetServicePropertiesPreparer prepares the SetServiceProperties request.
+func (client Client) SetServicePropertiesPreparer(ctx context.Context, accountName string, properties StorageServiceProperties) (*http.Request, error) {
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "properties"),
+ "restype": autorest.Encode("path", "service"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithXML(properties),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetServicePropertiesSender sends the SetServiceProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetServicePropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetServicePropertiesResponder handles the response to the SetServiceProperties request. The method always
+// closes the http.Response Body.
+func (client Client) SetServicePropertiesResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/queue/queues/resource_id.go b/storage/2018-03-28/queue/queues/resource_id.go
new file mode 100644
index 0000000..ee28b8b
--- /dev/null
+++ b/storage/2018-03-28/queue/queues/resource_id.go
@@ -0,0 +1,46 @@
+package queues
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Queue
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, queueName string) string {
+ domain := endpoints.GetQueueEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s", domain, queueName)
+}
+
+type ResourceID struct {
+ AccountName string
+ QueueName string
+}
+
+// ParseResourceID parses the Resource ID and returns an Object which
+// can be used to interact with a Queue within a Storage Account
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://foo.queue.core.windows.net/Bar
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ queueName := strings.TrimPrefix(uri.Path, "/")
+ return &ResourceID{
+ AccountName: *accountName,
+ QueueName: queueName,
+ }, nil
+}
diff --git a/storage/2018-03-28/queue/queues/resource_id_test.go b/storage/2018-03-28/queue/queues/resource_id_test.go
new file mode 100644
index 0000000..89323d7
--- /dev/null
+++ b/storage/2018-03-28/queue/queues/resource_id_test.go
@@ -0,0 +1,79 @@
+package queues
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.queue.core.chinacloudapi.cn/queue1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.queue.core.cloudapi.de/queue1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.queue.core.windows.net/queue1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.queue.core.usgovcloudapi.net/queue1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "queue1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.queue.core.chinacloudapi.cn/queue1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.queue.core.cloudapi.de/queue1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.queue.core.windows.net/queue1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.queue.core.usgovcloudapi.net/queue1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected the account name to be `account1` but got %q", actual.AccountName)
+ }
+
+ if actual.QueueName != "queue1" {
+ t.Fatalf("Expected the queue name to be `queue1` but got %q", actual.QueueName)
+ }
+ }
+}
diff --git a/storage/2018-03-28/queue/queues/version.go b/storage/2018-03-28/queue/queues/version.go
new file mode 100644
index 0000000..98ef659
--- /dev/null
+++ b/storage/2018-03-28/queue/queues/version.go
@@ -0,0 +1,14 @@
+package queues
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2018-03-28"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2018-03-28/table/entities/README.md b/storage/2018-03-28/table/entities/README.md
new file mode 100644
index 0000000..ff72d35
--- /dev/null
+++ b/storage/2018-03-28/table/entities/README.md
@@ -0,0 +1,48 @@
+## Table Storage Entities SDK for API version 2018-03-28
+
+This package allows you to interact with the Entities Table Storage API
+
+### Supported Authorizers
+
+* SharedKeyLite (Table)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/table/entities"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ tableName := "mytable"
+
+ storageAuth := autorest.NewSharedKeyLiteTableAuthorizer(accountName, storageAccountKey)
+ entitiesClient := entities.New()
+ entitiesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ input := entities.InsertEntityInput{
+ PartitionKey: "abc",
+ RowKey: "123",
+ MetaDataLevel: entities.NoMetaData,
+ Entity: map[string]interface{}{
+ "title": "Don't Kill My Vibe",
+ "artist": "Sigrid",
+ },
+ }
+ if _, err := entitiesClient.Insert(ctx, accountName, tableName, input); err != nil {
+ return fmt.Errorf("Error creating Entity: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2018-03-28/table/entities/client.go b/storage/2018-03-28/table/entities/client.go
new file mode 100644
index 0000000..17e9d75
--- /dev/null
+++ b/storage/2018-03-28/table/entities/client.go
@@ -0,0 +1,25 @@
+package entities
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for Table Storage Shares.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2018-03-28/table/entities/delete.go b/storage/2018-03-28/table/entities/delete.go
new file mode 100644
index 0000000..83e9188
--- /dev/null
+++ b/storage/2018-03-28/table/entities/delete.go
@@ -0,0 +1,99 @@
+package entities
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type DeleteEntityInput struct {
+ // When inserting an entity into a table, you must specify values for the PartitionKey and RowKey system properties.
+ // Together, these properties form the primary key and must be unique within the table.
+ // Both the PartitionKey and RowKey values must be string values; each key value may be up to 64 KB in size.
+ // If you are using an integer value for the key value, you should convert the integer to a fixed-width string,
+ // because they are canonically sorted. For example, you should convert the value 1 to 0000001 to ensure proper sorting.
+ RowKey string
+ PartitionKey string
+}
+
+// Delete deletes an existing entity in a table.
+func (client Client) Delete(ctx context.Context, accountName, tableName string, input DeleteEntityInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("entities.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("entities.Client", "Delete", "`tableName` cannot be an empty string.")
+ }
+ if input.PartitionKey == "" {
+ return result, validation.NewError("entities.Client", "Delete", "`input.PartitionKey` cannot be an empty string.")
+ }
+ if input.RowKey == "" {
+ return result, validation.NewError("entities.Client", "Delete", "`input.RowKey` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, tableName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "entities.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, tableName string, input DeleteEntityInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ "partitionKey": autorest.Encode("path", input.PartitionKey),
+ "rowKey": autorest.Encode("path", input.RowKey),
+ }
+
+ headers := map[string]interface{}{
+ // TODO: support for eTags
+ "If-Match": "*",
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}(PartitionKey='{partitionKey}', RowKey='{rowKey}')", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/table/entities/get.go b/storage/2018-03-28/table/entities/get.go
new file mode 100644
index 0000000..bdb4018
--- /dev/null
+++ b/storage/2018-03-28/table/entities/get.go
@@ -0,0 +1,108 @@
+package entities
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetEntityInput struct {
+ PartitionKey string
+ RowKey string
+
+ // The Level of MetaData which should be returned
+ MetaDataLevel MetaDataLevel
+}
+
+type GetEntityResult struct {
+ autorest.Response
+
+ Entity map[string]interface{}
+}
+
+// Get queries entities in a table and includes the $filter and $select options.
+func (client Client) Get(ctx context.Context, accountName, tableName string, input GetEntityInput) (result GetEntityResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("entities.Client", "Get", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("entities.Client", "Get", "`tableName` cannot be an empty string.")
+ }
+ if input.PartitionKey == "" {
+ return result, validation.NewError("entities.Client", "Get", "`input.PartitionKey` cannot be an empty string.")
+ }
+ if input.RowKey == "" {
+ return result, validation.NewError("entities.Client", "Get", "`input.RowKey` cannot be an empty string.")
+ }
+
+ req, err := client.GetPreparer(ctx, accountName, tableName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Get", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "entities.Client", "Get", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Get", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPreparer prepares the Get request.
+func (client Client) GetPreparer(ctx context.Context, accountName, tableName string, input GetEntityInput) (*http.Request, error) {
+
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ "partitionKey": autorest.Encode("path", input.PartitionKey),
+ "rowKey": autorest.Encode("path", input.RowKey),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "Accept": fmt.Sprintf("application/json;odata=%s", input.MetaDataLevel),
+ "DataServiceVersion": "3.0;NetFx",
+ "MaxDataServiceVersion": "3.0;NetFx",
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}(PartitionKey='{partitionKey}',RowKey='{rowKey}')", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetSender sends the Get request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetResponder handles the response to the Get request. The method always
+// closes the http.Response Body.
+func (client Client) GetResponder(resp *http.Response) (result GetEntityResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingJSON(&result.Entity),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/table/entities/insert.go b/storage/2018-03-28/table/entities/insert.go
new file mode 100644
index 0000000..92b05ce
--- /dev/null
+++ b/storage/2018-03-28/table/entities/insert.go
@@ -0,0 +1,112 @@
+package entities
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type InsertEntityInput struct {
+ // The level of MetaData provided for this Entity
+ MetaDataLevel MetaDataLevel
+
+ // The Entity which should be inserted, by default all values are strings
+ // To explicitly type a property, specify the appropriate OData data type by setting
+ // the m:type attribute within the property definition
+ Entity map[string]interface{}
+
+ // When inserting an entity into a table, you must specify values for the PartitionKey and RowKey system properties.
+ // Together, these properties form the primary key and must be unique within the table.
+ // Both the PartitionKey and RowKey values must be string values; each key value may be up to 64 KB in size.
+ // If you are using an integer value for the key value, you should convert the integer to a fixed-width string,
+ // because they are canonically sorted. For example, you should convert the value 1 to 0000001 to ensure proper sorting.
+ RowKey string
+ PartitionKey string
+}
+
+// Insert inserts a new entity into a table.
+func (client Client) Insert(ctx context.Context, accountName, tableName string, input InsertEntityInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("entities.Client", "Insert", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("entities.Client", "Insert", "`tableName` cannot be an empty string.")
+ }
+ if input.PartitionKey == "" {
+ return result, validation.NewError("entities.Client", "Insert", "`input.PartitionKey` cannot be an empty string.")
+ }
+ if input.RowKey == "" {
+ return result, validation.NewError("entities.Client", "Insert", "`input.RowKey` cannot be an empty string.")
+ }
+
+ req, err := client.InsertPreparer(ctx, accountName, tableName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Insert", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.InsertSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "entities.Client", "Insert", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.InsertResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Insert", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// InsertPreparer prepares the Insert request.
+func (client Client) InsertPreparer(ctx context.Context, accountName, tableName string, input InsertEntityInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "Accept": fmt.Sprintf("application/json;odata=%s", input.MetaDataLevel),
+ "Prefer": "return-no-content",
+ }
+
+ input.Entity["PartitionKey"] = input.PartitionKey
+ input.Entity["RowKey"] = input.RowKey
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/json"),
+ autorest.AsPost(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}", pathParameters),
+ autorest.WithJSON(input.Entity),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// InsertSender sends the Insert request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) InsertSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// InsertResponder handles the response to the Insert request. The method always
+// closes the http.Response Body.
+func (client Client) InsertResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/table/entities/insert_or_merge.go b/storage/2018-03-28/table/entities/insert_or_merge.go
new file mode 100644
index 0000000..1fb4ed3
--- /dev/null
+++ b/storage/2018-03-28/table/entities/insert_or_merge.go
@@ -0,0 +1,108 @@
+package entities
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type InsertOrMergeEntityInput struct {
+ // The Entity which should be inserted, by default all values are strings
+ // To explicitly type a property, specify the appropriate OData data type by setting
+ // the m:type attribute within the property definition
+ Entity map[string]interface{}
+
+ // When inserting an entity into a table, you must specify values for the PartitionKey and RowKey system properties.
+ // Together, these properties form the primary key and must be unique within the table.
+ // Both the PartitionKey and RowKey values must be string values; each key value may be up to 64 KB in size.
+ // If you are using an integer value for the key value, you should convert the integer to a fixed-width string,
+ // because they are canonically sorted. For example, you should convert the value 1 to 0000001 to ensure proper sorting.
+ RowKey string
+ PartitionKey string
+}
+
+// InsertOrMerge updates an existing entity or inserts a new entity if it does not exist in the table.
+// Because this operation can insert or update an entity, it is also known as an upsert operation.
+func (client Client) InsertOrMerge(ctx context.Context, accountName, tableName string, input InsertOrMergeEntityInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("entities.Client", "InsertOrMerge", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("entities.Client", "InsertOrMerge", "`tableName` cannot be an empty string.")
+ }
+ if input.PartitionKey == "" {
+ return result, validation.NewError("entities.Client", "InsertOrMerge", "`input.PartitionKey` cannot be an empty string.")
+ }
+ if input.RowKey == "" {
+ return result, validation.NewError("entities.Client", "InsertOrMerge", "`input.RowKey` cannot be an empty string.")
+ }
+
+ req, err := client.InsertOrMergePreparer(ctx, accountName, tableName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "InsertOrMerge", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.InsertOrMergeSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "entities.Client", "InsertOrMerge", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.InsertOrMergeResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "InsertOrMerge", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// InsertOrMergePreparer prepares the InsertOrMerge request.
+func (client Client) InsertOrMergePreparer(ctx context.Context, accountName, tableName string, input InsertOrMergeEntityInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ "partitionKey": autorest.Encode("path", input.PartitionKey),
+ "rowKey": autorest.Encode("path", input.RowKey),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "Accept": "application/json",
+ "Prefer": "return-no-content",
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/json"),
+ autorest.AsMerge(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}(PartitionKey='{partitionKey}', RowKey='{rowKey}')", pathParameters),
+ autorest.WithJSON(input.Entity),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// InsertOrMergeSender sends the InsertOrMerge request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) InsertOrMergeSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// InsertOrMergeResponder handles the response to the InsertOrMerge request. The method always
+// closes the http.Response Body.
+func (client Client) InsertOrMergeResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/table/entities/insert_or_replace.go b/storage/2018-03-28/table/entities/insert_or_replace.go
new file mode 100644
index 0000000..036ba5d
--- /dev/null
+++ b/storage/2018-03-28/table/entities/insert_or_replace.go
@@ -0,0 +1,108 @@
+package entities
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type InsertOrReplaceEntityInput struct {
+ // The Entity which should be inserted, by default all values are strings
+ // To explicitly type a property, specify the appropriate OData data type by setting
+ // the m:type attribute within the property definition
+ Entity map[string]interface{}
+
+ // When inserting an entity into a table, you must specify values for the PartitionKey and RowKey system properties.
+ // Together, these properties form the primary key and must be unique within the table.
+ // Both the PartitionKey and RowKey values must be string values; each key value may be up to 64 KB in size.
+ // If you are using an integer value for the key value, you should convert the integer to a fixed-width string,
+ // because they are canonically sorted. For example, you should convert the value 1 to 0000001 to ensure proper sorting.
+ RowKey string
+ PartitionKey string
+}
+
+// InsertOrReplace replaces an existing entity or inserts a new entity if it does not exist in the table.
+// Because this operation can insert or update an entity, it is also known as an upsert operation.
+func (client Client) InsertOrReplace(ctx context.Context, accountName, tableName string, input InsertOrReplaceEntityInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("entities.Client", "InsertOrReplace", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("entities.Client", "InsertOrReplace", "`tableName` cannot be an empty string.")
+ }
+ if input.PartitionKey == "" {
+ return result, validation.NewError("entities.Client", "InsertOrReplace", "`input.PartitionKey` cannot be an empty string.")
+ }
+ if input.RowKey == "" {
+ return result, validation.NewError("entities.Client", "InsertOrReplace", "`input.RowKey` cannot be an empty string.")
+ }
+
+ req, err := client.InsertOrReplacePreparer(ctx, accountName, tableName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "InsertOrReplace", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.InsertOrReplaceSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "entities.Client", "InsertOrReplace", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.InsertOrReplaceResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "InsertOrReplace", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// InsertOrReplacePreparer prepares the InsertOrReplace request.
+func (client Client) InsertOrReplacePreparer(ctx context.Context, accountName, tableName string, input InsertOrReplaceEntityInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ "partitionKey": autorest.Encode("path", input.PartitionKey),
+ "rowKey": autorest.Encode("path", input.RowKey),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "Accept": "application/json",
+ "Prefer": "return-no-content",
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/json"),
+ autorest.AsMerge(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}(PartitionKey='{partitionKey}', RowKey='{rowKey}')", pathParameters),
+ autorest.WithJSON(input.Entity),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// InsertOrReplaceSender sends the InsertOrReplace request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) InsertOrReplaceSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// InsertOrReplaceResponder handles the response to the InsertOrReplace request. The method always
+// closes the http.Response Body.
+func (client Client) InsertOrReplaceResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/table/entities/lifecycle_test.go b/storage/2018-03-28/table/entities/lifecycle_test.go
new file mode 100644
index 0000000..9f15722
--- /dev/null
+++ b/storage/2018-03-28/table/entities/lifecycle_test.go
@@ -0,0 +1,135 @@
+package entities
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/table/tables"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestEntitiesLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ tableName := fmt.Sprintf("table%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteTableAuthorizer(accountName, testData.StorageAccountKey)
+ tablesClient := tables.NewWithEnvironment(client.Environment)
+ tablesClient.Client = client.PrepareWithAuthorizer(tablesClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Creating Table..")
+ if _, err := tablesClient.Create(ctx, accountName, tableName); err != nil {
+ t.Fatalf("Error creating Table %q: %s", tableName, err)
+ }
+ defer tablesClient.Delete(ctx, accountName, tableName)
+
+ entitiesClient := NewWithEnvironment(client.Environment)
+ entitiesClient.Client = client.PrepareWithAuthorizer(entitiesClient.Client, storageAuth)
+
+ partitionKey := "hello"
+ rowKey := "there"
+
+ t.Logf("[DEBUG] Inserting..")
+ insertInput := InsertEntityInput{
+ MetaDataLevel: NoMetaData,
+ PartitionKey: partitionKey,
+ RowKey: rowKey,
+ Entity: map[string]interface{}{
+ "hello": "world",
+ },
+ }
+ if _, err := entitiesClient.Insert(ctx, accountName, tableName, insertInput); err != nil {
+ t.Logf("Error retrieving: %s", err)
+ }
+
+ t.Logf("[DEBUG] Insert or Merging..")
+ insertOrMergeInput := InsertOrMergeEntityInput{
+ PartitionKey: partitionKey,
+ RowKey: rowKey,
+ Entity: map[string]interface{}{
+ "hello": "ther88e",
+ },
+ }
+ if _, err := entitiesClient.InsertOrMerge(ctx, accountName, tableName, insertOrMergeInput); err != nil {
+ t.Logf("Error insert/merging: %s", err)
+ }
+
+ t.Logf("[DEBUG] Insert or Replacing..")
+ insertOrReplaceInput := InsertOrReplaceEntityInput{
+ PartitionKey: partitionKey,
+ RowKey: rowKey,
+ Entity: map[string]interface{}{
+ "hello": "pandas",
+ },
+ }
+ if _, err := entitiesClient.InsertOrReplace(ctx, accountName, tableName, insertOrReplaceInput); err != nil {
+ t.Logf("Error inserting/replacing: %s", err)
+ }
+
+ t.Logf("[DEBUG] Querying..")
+ queryInput := QueryEntitiesInput{
+ MetaDataLevel: NoMetaData,
+ }
+ results, err := entitiesClient.Query(ctx, accountName, tableName, queryInput)
+ if err != nil {
+ t.Logf("Error querying: %s", err)
+ }
+
+ if len(results.Entities) != 1 {
+ t.Fatalf("Expected 1 item but got %d", len(results.Entities))
+ }
+
+ for _, v := range results.Entities {
+ thisPartitionKey := v["PartitionKey"].(string)
+ thisRowKey := v["RowKey"].(string)
+ if partitionKey != thisPartitionKey {
+ t.Fatalf("Expected Partition Key to be %q but got %q", partitionKey, thisPartitionKey)
+ }
+ if rowKey != thisRowKey {
+ t.Fatalf("Expected Partition Key to be %q but got %q", rowKey, thisRowKey)
+ }
+ }
+
+ t.Logf("[DEBUG] Retrieving..")
+ getInput := GetEntityInput{
+ MetaDataLevel: MinimalMetaData,
+ PartitionKey: partitionKey,
+ RowKey: rowKey,
+ }
+ getResults, err := entitiesClient.Get(ctx, accountName, tableName, getInput)
+ if err != nil {
+ t.Logf("Error querying: %s", err)
+ }
+
+ partitionKey2 := getResults.Entity["PartitionKey"].(string)
+ rowKey2 := getResults.Entity["RowKey"].(string)
+ if partitionKey2 != partitionKey {
+ t.Fatalf("Expected Partition Key to be %q but got %q", partitionKey, partitionKey2)
+ }
+ if rowKey2 != rowKey {
+ t.Fatalf("Expected Row Key to be %q but got %q", rowKey, rowKey2)
+ }
+
+ t.Logf("[DEBUG] Deleting..")
+ deleteInput := DeleteEntityInput{
+ PartitionKey: partitionKey,
+ RowKey: rowKey,
+ }
+ if _, err := entitiesClient.Delete(ctx, accountName, tableName, deleteInput); err != nil {
+ t.Logf("Error deleting: %s", err)
+ }
+}
diff --git a/storage/2018-03-28/table/entities/models.go b/storage/2018-03-28/table/entities/models.go
new file mode 100644
index 0000000..e3c6ccc
--- /dev/null
+++ b/storage/2018-03-28/table/entities/models.go
@@ -0,0 +1,9 @@
+package entities
+
+type MetaDataLevel string
+
+var (
+ NoMetaData MetaDataLevel = "nometadata"
+ MinimalMetaData MetaDataLevel = "minimalmetadata"
+ FullMetaData MetaDataLevel = "fullmetadata"
+)
diff --git a/storage/2018-03-28/table/entities/query.go b/storage/2018-03-28/table/entities/query.go
new file mode 100644
index 0000000..a768b83
--- /dev/null
+++ b/storage/2018-03-28/table/entities/query.go
@@ -0,0 +1,155 @@
+package entities
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type QueryEntitiesInput struct {
+ // An optional OData filter
+ Filter *string
+
+ // An optional comma-separated
+ PropertyNamesToSelect *[]string
+
+ PartitionKey string
+ RowKey string
+
+ // The Level of MetaData which should be returned
+ MetaDataLevel MetaDataLevel
+
+ // The Next Partition Key used to load data from a previous point
+ NextPartitionKey *string
+
+ // The Next Row Key used to load data from a previous point
+ NextRowKey *string
+}
+
+type QueryEntitiesResult struct {
+ autorest.Response
+
+ NextPartitionKey string
+ NextRowKey string
+
+ MetaData string `json:"odata.metadata,omitempty"`
+ Entities []map[string]interface{} `json:"value"`
+}
+
+// Query queries entities in a table and includes the $filter and $select options.
+func (client Client) Query(ctx context.Context, accountName, tableName string, input QueryEntitiesInput) (result QueryEntitiesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("entities.Client", "Query", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("entities.Client", "Query", "`tableName` cannot be an empty string.")
+ }
+
+ req, err := client.QueryPreparer(ctx, accountName, tableName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Query", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.QuerySender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "entities.Client", "Query", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.QueryResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Query", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// QueryPreparer prepares the Query request.
+func (client Client) QueryPreparer(ctx context.Context, accountName, tableName string, input QueryEntitiesInput) (*http.Request, error) {
+
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ "additionalParameters": "",
+ }
+
+ //PartitionKey='',RowKey=''
+ additionalParams := make([]string, 0)
+ if input.PartitionKey != "" {
+ additionalParams = append(additionalParams, fmt.Sprintf("PartitionKey='%s'", input.PartitionKey))
+ }
+ if input.RowKey != "" {
+ additionalParams = append(additionalParams, fmt.Sprintf("RowKey='%s'", input.RowKey))
+ }
+ if len(additionalParams) > 0 {
+ pathParameters["additionalParameters"] = autorest.Encode("path", strings.Join(additionalParams, ","))
+ }
+
+ queryParameters := map[string]interface{}{}
+
+ if input.Filter != nil {
+ queryParameters["filter"] = autorest.Encode("query", input.Filter)
+ }
+
+ if input.PropertyNamesToSelect != nil {
+ queryParameters["$select"] = autorest.Encode("query", strings.Join(*input.PropertyNamesToSelect, ","))
+ }
+
+ if input.NextPartitionKey != nil {
+ queryParameters["NextPartitionKey"] = *input.NextPartitionKey
+ }
+
+ if input.NextRowKey != nil {
+ queryParameters["NextRowKey"] = *input.NextRowKey
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "Accept": fmt.Sprintf("application/json;odata=%s", input.MetaDataLevel),
+ "DataServiceVersion": "3.0;NetFx",
+ "MaxDataServiceVersion": "3.0;NetFx",
+ }
+
+ // GET /myaccount/Customers()?$filter=(Rating%20ge%203)%20and%20(Rating%20le%206)&$select=PartitionKey,RowKey,Address,CustomerSince
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}({additionalParameters})", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// QuerySender sends the Query request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) QuerySender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// QueryResponder handles the response to the Query request. The method always
+// closes the http.Response Body.
+func (client Client) QueryResponder(resp *http.Response) (result QueryEntitiesResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.NextPartitionKey = resp.Header.Get("x-ms-continuation-NextPartitionKey")
+ result.NextRowKey = resp.Header.Get("x-ms-continuation-NextRowKey")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingJSON(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/table/entities/resource_id.go b/storage/2018-03-28/table/entities/resource_id.go
new file mode 100644
index 0000000..59366a2
--- /dev/null
+++ b/storage/2018-03-28/table/entities/resource_id.go
@@ -0,0 +1,91 @@
+package entities
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Entity
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, tableName, partitionKey, rowKey string) string {
+ domain := endpoints.GetTableEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s(PartitionKey='%s',RowKey='%s')", domain, tableName, partitionKey, rowKey)
+}
+
+type ResourceID struct {
+ AccountName string
+ TableName string
+ PartitionKey string
+ RowKey string
+}
+
+// ParseResourceID parses the specified Resource ID and returns an object which
+// can be used to look up the specified Entity within the specified Table
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://account1.table.core.chinacloudapi.cn/table1(PartitionKey='partition1',RowKey='row1')
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ // assume there a `Table('')`
+ path := strings.TrimPrefix(uri.Path, "/")
+ if !strings.Contains(uri.Path, "(") || !strings.HasSuffix(uri.Path, ")") {
+ return nil, fmt.Errorf("Expected the Table Name to be in the format `tables(PartitionKey='',RowKey='')` but got %q", path)
+ }
+
+ // NOTE: honestly this could probably be a RegEx, but this seemed like the simplest way to
+ // allow these two fields to be specified in either order
+ indexOfBracket := strings.IndexByte(path, '(')
+ tableName := path[0:indexOfBracket]
+
+ // trim off the brackets
+ temp := strings.TrimPrefix(path, fmt.Sprintf("%s(", tableName))
+ temp = strings.TrimSuffix(temp, ")")
+
+ dictionary := strings.Split(temp, ",")
+ partitionKey := ""
+ rowKey := ""
+ for _, v := range dictionary {
+ split := strings.Split(v, "=")
+ if len(split) != 2 {
+ return nil, fmt.Errorf("Expected 2 segments but got %d for %q", len(split), v)
+ }
+
+ key := split[0]
+ value := strings.TrimSuffix(strings.TrimPrefix(split[1], "'"), "'")
+ if strings.EqualFold(key, "PartitionKey") {
+ partitionKey = value
+ } else if strings.EqualFold(key, "RowKey") {
+ rowKey = value
+ } else {
+ return nil, fmt.Errorf("Unexpected Key %q", key)
+ }
+ }
+
+ if partitionKey == "" {
+ return nil, fmt.Errorf("Expected a PartitionKey but didn't get one")
+ }
+ if rowKey == "" {
+ return nil, fmt.Errorf("Expected a RowKey but didn't get one")
+ }
+
+ return &ResourceID{
+ AccountName: *accountName,
+ TableName: tableName,
+ PartitionKey: partitionKey,
+ RowKey: rowKey,
+ }, nil
+}
diff --git a/storage/2018-03-28/table/entities/resource_id_test.go b/storage/2018-03-28/table/entities/resource_id_test.go
new file mode 100644
index 0000000..e85af79
--- /dev/null
+++ b/storage/2018-03-28/table/entities/resource_id_test.go
@@ -0,0 +1,84 @@
+package entities
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.table.core.chinacloudapi.cn/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.table.core.cloudapi.de/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.table.core.windows.net/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.table.core.usgovcloudapi.net/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "table1", "partition1", "row1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.table.core.chinacloudapi.cn/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.table.core.cloudapi.de/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.table.core.windows.net/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.table.core.usgovcloudapi.net/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.TableName != "table1" {
+ t.Fatalf("Expected Table Name to be `table1` but got %q", actual.TableName)
+ }
+ if actual.PartitionKey != "partition1" {
+ t.Fatalf("Expected Partition Key to be `partition1` but got %q", actual.PartitionKey)
+ }
+ if actual.RowKey != "row1" {
+ t.Fatalf("Expected Row Key to be `row1` but got %q", actual.RowKey)
+ }
+ }
+}
diff --git a/storage/2018-03-28/table/entities/version.go b/storage/2018-03-28/table/entities/version.go
new file mode 100644
index 0000000..064c70c
--- /dev/null
+++ b/storage/2018-03-28/table/entities/version.go
@@ -0,0 +1,14 @@
+package entities
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2018-03-28"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2018-03-28/table/tables/README.md b/storage/2018-03-28/table/tables/README.md
new file mode 100644
index 0000000..ca03d8f
--- /dev/null
+++ b/storage/2018-03-28/table/tables/README.md
@@ -0,0 +1,39 @@
+## Table Storage Tables SDK for API version 2018-03-28
+
+This package allows you to interact with the Tables Table Storage API
+
+### Supported Authorizers
+
+* SharedKeyLite (Table)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2018-03-28/table/tables"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ tableName := "mytable"
+
+ storageAuth := autorest.NewSharedKeyLiteTableAuthorizer(accountName, storageAccountKey)
+ tablesClient := tables.New()
+ tablesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ if _, err := tablesClient.Insert(ctx, accountName, tableName); err != nil {
+ return fmt.Errorf("Error creating Table: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2018-03-28/table/tables/acl_get.go b/storage/2018-03-28/table/tables/acl_get.go
new file mode 100644
index 0000000..0ef0000
--- /dev/null
+++ b/storage/2018-03-28/table/tables/acl_get.go
@@ -0,0 +1,93 @@
+package tables
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetACLResult struct {
+ autorest.Response
+
+ SignedIdentifiers []SignedIdentifier `xml:"SignedIdentifier"`
+}
+
+// GetACL returns the Access Control List for the specified Table
+func (client Client) GetACL(ctx context.Context, accountName, tableName string) (result GetACLResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("tables.Client", "GetACL", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("tables.Client", "GetACL", "`tableName` cannot be an empty string.")
+ }
+
+ req, err := client.GetACLPreparer(ctx, accountName, tableName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "GetACL", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetACLSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "tables.Client", "GetACL", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetACLResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "GetACL", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetACLPreparer prepares the GetACL request.
+func (client Client) GetACLPreparer(ctx context.Context, accountName, tableName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "acl"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetACLSender sends the GetACL request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetACLSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetACLResponder handles the response to the GetACL request. The method always
+// closes the http.Response Body.
+func (client Client) GetACLResponder(resp *http.Response) (result GetACLResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/table/tables/acl_set.go b/storage/2018-03-28/table/tables/acl_set.go
new file mode 100644
index 0000000..c26bffc
--- /dev/null
+++ b/storage/2018-03-28/table/tables/acl_set.go
@@ -0,0 +1,98 @@
+package tables
+
+import (
+ "context"
+ "encoding/xml"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type setAcl struct {
+ SignedIdentifiers []SignedIdentifier `xml:"SignedIdentifier"`
+
+ XMLName xml.Name `xml:"SignedIdentifiers"`
+}
+
+// SetACL sets the specified Access Control List for the specified Table
+func (client Client) SetACL(ctx context.Context, accountName, tableName string, acls []SignedIdentifier) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("tables.Client", "SetACL", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("tables.Client", "SetACL", "`tableName` cannot be an empty string.")
+ }
+
+ req, err := client.SetACLPreparer(ctx, accountName, tableName, acls)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "SetACL", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetACLSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "tables.Client", "SetACL", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetACLResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "SetACL", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetACLPreparer prepares the SetACL request.
+func (client Client) SetACLPreparer(ctx context.Context, accountName, tableName string, acls []SignedIdentifier) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "acl"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ input := setAcl{
+ SignedIdentifiers: acls,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithXML(&input))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetACLSender sends the SetACL request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetACLSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetACLResponder handles the response to the SetACL request. The method always
+// closes the http.Response Body.
+func (client Client) SetACLResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/table/tables/client.go b/storage/2018-03-28/table/tables/client.go
new file mode 100644
index 0000000..56724b9
--- /dev/null
+++ b/storage/2018-03-28/table/tables/client.go
@@ -0,0 +1,25 @@
+package tables
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for Table Storage Shares.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2018-03-28/table/tables/create.go b/storage/2018-03-28/table/tables/create.go
new file mode 100644
index 0000000..561f574
--- /dev/null
+++ b/storage/2018-03-28/table/tables/create.go
@@ -0,0 +1,90 @@
+package tables
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type createTableRequest struct {
+ TableName string `json:"TableName"`
+}
+
+// Create creates a new table in the storage account.
+func (client Client) Create(ctx context.Context, accountName, tableName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("tables.Client", "Create", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("tables.Client", "Create", "`tableName` cannot be an empty string.")
+ }
+
+ req, err := client.CreatePreparer(ctx, accountName, tableName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Create", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "tables.Client", "Create", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Create", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreatePreparer prepares the Create request.
+func (client Client) CreatePreparer(ctx context.Context, accountName, tableName string) (*http.Request, error) {
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ // NOTE: we could support returning metadata here, but it doesn't appear to be directly useful
+ // vs making a request using the Get methods as-necessary?
+ "Accept": "application/json;odata=nometadata",
+ "Prefer": "return-no-content",
+ }
+
+ body := createTableRequest{
+ TableName: tableName,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/json"),
+ autorest.AsPost(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPath("/Tables"),
+ autorest.WithJSON(body),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSender sends the Create request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateResponder handles the response to the Create request. The method always
+// closes the http.Response Body.
+func (client Client) CreateResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/table/tables/delete.go b/storage/2018-03-28/table/tables/delete.go
new file mode 100644
index 0000000..5b5ec86
--- /dev/null
+++ b/storage/2018-03-28/table/tables/delete.go
@@ -0,0 +1,79 @@
+package tables
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete deletes the specified table and any data it contains.
+func (client Client) Delete(ctx context.Context, accountName, tableName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("tables.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("tables.Client", "Delete", "`tableName` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, tableName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "tables.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, tableName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ }
+
+ // NOTE: whilst the API documentation says that API Version is Optional
+ // apparently specifying it causes an "invalid content type" to always be returned
+ // as such we omit it here :shrug:
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/Tables('{tableName}')", pathParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/table/tables/exists.go b/storage/2018-03-28/table/tables/exists.go
new file mode 100644
index 0000000..b3a2718
--- /dev/null
+++ b/storage/2018-03-28/table/tables/exists.go
@@ -0,0 +1,80 @@
+package tables
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Exists checks that the specified table exists
+func (client Client) Exists(ctx context.Context, accountName, tableName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("tables.Client", "Exists", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("tables.Client", "Exists", "`tableName` cannot be an empty string.")
+ }
+
+ req, err := client.ExistsPreparer(ctx, accountName, tableName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Exists", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ExistsSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "tables.Client", "Exists", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ExistsResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Exists", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ExistsPreparer prepares the Exists request.
+func (client Client) ExistsPreparer(ctx context.Context, accountName, tableName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ }
+
+ // NOTE: whilst the API documentation says that API Version is Optional
+ // apparently specifying it causes an "invalid content type" to always be returned
+ // as such we omit it here :shrug:
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.AsContentType("application/xml"),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/Tables('{tableName}')", pathParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ExistsSender sends the Exists request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ExistsSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ExistsResponder handles the response to the Exists request. The method always
+// closes the http.Response Body.
+func (client Client) ExistsResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/table/tables/lifecycle_test.go b/storage/2018-03-28/table/tables/lifecycle_test.go
new file mode 100644
index 0000000..74ab0fe
--- /dev/null
+++ b/storage/2018-03-28/table/tables/lifecycle_test.go
@@ -0,0 +1,112 @@
+package tables
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestTablesLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ tableName := fmt.Sprintf("table%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteTableAuthorizer(accountName, testData.StorageAccountKey)
+ tablesClient := NewWithEnvironment(client.Environment)
+ tablesClient.Client = client.PrepareWithAuthorizer(tablesClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Creating Table..")
+ if _, err := tablesClient.Create(ctx, accountName, tableName); err != nil {
+ t.Fatalf("Error creating Table %q: %s", tableName, err)
+ }
+
+ // first look it up directly and confirm it's there
+ t.Logf("[DEBUG] Checking if Table exists..")
+ if _, err := tablesClient.Exists(ctx, accountName, tableName); err != nil {
+ t.Fatalf("Error checking if Table %q exists: %s", tableName, err)
+ }
+
+ // then confirm it exists in the Query too
+ t.Logf("[DEBUG] Querying for Tables..")
+ result, err := tablesClient.Query(ctx, accountName, NoMetaData)
+ if err != nil {
+ t.Fatalf("Error retrieving Tables: %s", err)
+ }
+ found := false
+ for _, v := range result.Tables {
+ log.Printf("[DEBUG] Table: %q", v.TableName)
+
+ if v.TableName == tableName {
+ found = true
+ }
+ }
+ if !found {
+ t.Fatalf("%q was not found in the Query response!", tableName)
+ }
+
+ t.Logf("[DEBUG] Setting ACL's for Table %q..", tableName)
+ acls := []SignedIdentifier{
+ {
+ Id: "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=",
+ AccessPolicy: AccessPolicy{
+ Permission: "raud",
+ Start: "2020-11-26T08:49:37.0000000Z",
+ Expiry: "2020-11-27T08:49:37.0000000Z",
+ },
+ },
+ }
+ if _, err := tablesClient.SetACL(ctx, accountName, tableName, acls); err != nil {
+ t.Fatalf("Error setting ACLs: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving ACL's for Table %q..", tableName)
+ retrievedACLs, err := tablesClient.GetACL(ctx, accountName, tableName)
+ if err != nil {
+ t.Fatalf("Error retrieving ACLs: %s", err)
+ }
+
+ if len(retrievedACLs.SignedIdentifiers) != len(acls) {
+ t.Fatalf("Expected %d but got %q ACLs", len(retrievedACLs.SignedIdentifiers), len(acls))
+ }
+
+ for i, retrievedAcl := range retrievedACLs.SignedIdentifiers {
+ expectedAcl := acls[i]
+
+ if retrievedAcl.Id != expectedAcl.Id {
+ t.Fatalf("Expected ID to be %q but got %q", retrievedAcl.Id, expectedAcl.Id)
+ }
+
+ if retrievedAcl.AccessPolicy.Start != expectedAcl.AccessPolicy.Start {
+ t.Fatalf("Expected Start to be %q but got %q", retrievedAcl.AccessPolicy.Start, expectedAcl.AccessPolicy.Start)
+ }
+
+ if retrievedAcl.AccessPolicy.Expiry != expectedAcl.AccessPolicy.Expiry {
+ t.Fatalf("Expected Expiry to be %q but got %q", retrievedAcl.AccessPolicy.Expiry, expectedAcl.AccessPolicy.Expiry)
+ }
+
+ if retrievedAcl.AccessPolicy.Permission != expectedAcl.AccessPolicy.Permission {
+ t.Fatalf("Expected Permission to be %q but got %q", retrievedAcl.AccessPolicy.Permission, expectedAcl.AccessPolicy.Permission)
+ }
+ }
+
+ t.Logf("[DEBUG] Deleting Table %q..", tableName)
+ if _, err := tablesClient.Delete(ctx, accountName, tableName); err != nil {
+ t.Fatalf("Error deleting %q: %s", tableName, err)
+ }
+}
diff --git a/storage/2018-03-28/table/tables/models.go b/storage/2018-03-28/table/tables/models.go
new file mode 100644
index 0000000..d7c382a
--- /dev/null
+++ b/storage/2018-03-28/table/tables/models.go
@@ -0,0 +1,29 @@
+package tables
+
+type MetaDataLevel string
+
+var (
+ NoMetaData MetaDataLevel = "nometadata"
+ MinimalMetaData MetaDataLevel = "minimalmetadata"
+ FullMetaData MetaDataLevel = "fullmetadata"
+)
+
+type GetResultItem struct {
+ TableName string `json:"TableName"`
+
+ // Optional, depending on the MetaData Level
+ ODataType string `json:"odata.type,omitempty"`
+ ODataID string `json:"odata.id,omitEmpty"`
+ ODataEditLink string `json:"odata.editLink,omitEmpty"`
+}
+
+type SignedIdentifier struct {
+ Id string `xml:"Id"`
+ AccessPolicy AccessPolicy `xml:"AccessPolicy"`
+}
+
+type AccessPolicy struct {
+ Start string `xml:"Start"`
+ Expiry string `xml:"Expiry"`
+ Permission string `xml:"Permission"`
+}
diff --git a/storage/2018-03-28/table/tables/query.go b/storage/2018-03-28/table/tables/query.go
new file mode 100644
index 0000000..475370f
--- /dev/null
+++ b/storage/2018-03-28/table/tables/query.go
@@ -0,0 +1,87 @@
+package tables
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetResult struct {
+ autorest.Response
+
+ MetaData string `json:"odata.metadata,omitempty"`
+ Tables []GetResultItem `json:"value"`
+}
+
+// Query returns a list of tables under the specified account.
+func (client Client) Query(ctx context.Context, accountName string, metaDataLevel MetaDataLevel) (result GetResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("tables.Client", "Query", "`accountName` cannot be an empty string.")
+ }
+
+ req, err := client.QueryPreparer(ctx, accountName, metaDataLevel)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Query", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.QuerySender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "tables.Client", "Query", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.QueryResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Query", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// QueryPreparer prepares the Query request.
+func (client Client) QueryPreparer(ctx context.Context, accountName string, metaDataLevel MetaDataLevel) (*http.Request, error) {
+ // NOTE: whilst this supports ContinuationTokens and 'Top'
+ // it appears that 'Skip' returns a '501 Not Implemented'
+ // as such, we intentionally don't support those right now
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "Accept": fmt.Sprintf("application/json;odata=%s", metaDataLevel),
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPath("/Tables"),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// QuerySender sends the Query request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) QuerySender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// QueryResponder handles the response to the Query request. The method always
+// closes the http.Response Body.
+func (client Client) QueryResponder(resp *http.Response) (result GetResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingJSON(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-03-28/table/tables/resource_id.go b/storage/2018-03-28/table/tables/resource_id.go
new file mode 100644
index 0000000..1052317
--- /dev/null
+++ b/storage/2018-03-28/table/tables/resource_id.go
@@ -0,0 +1,54 @@
+package tables
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Table
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, tableName string) string {
+ domain := endpoints.GetTableEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/Tables('%s')", domain, tableName)
+}
+
+type ResourceID struct {
+ AccountName string
+ TableName string
+}
+
+// ParseResourceID parses the Resource ID and returns an object which
+// can be used to interact with the Table within the specified Storage Account
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://foo.table.core.windows.net/Table('foo')
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ // assume there a `Table('')`
+ path := strings.TrimPrefix(uri.Path, "/")
+ if !strings.HasPrefix(path, "Tables('") || !strings.HasSuffix(path, "')") {
+ return nil, fmt.Errorf("Expected the Table Name to be in the format `Tables('name')` but got %q", path)
+ }
+
+ // strip off the `Table('')`
+ tableName := strings.TrimPrefix(uri.Path, "/Tables('")
+ tableName = strings.TrimSuffix(tableName, "')")
+ return &ResourceID{
+ AccountName: *accountName,
+ TableName: tableName,
+ }, nil
+}
diff --git a/storage/2018-03-28/table/tables/resource_id_test.go b/storage/2018-03-28/table/tables/resource_id_test.go
new file mode 100644
index 0000000..5557f81
--- /dev/null
+++ b/storage/2018-03-28/table/tables/resource_id_test.go
@@ -0,0 +1,78 @@
+package tables
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.table.core.chinacloudapi.cn/Tables('table1')",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.table.core.cloudapi.de/Tables('table1')",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.table.core.windows.net/Tables('table1')",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.table.core.usgovcloudapi.net/Tables('table1')",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "table1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.table.core.chinacloudapi.cn/Tables('table1')",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.table.core.cloudapi.de/Tables('table1')",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.table.core.windows.net/Tables('table1')",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.table.core.usgovcloudapi.net/Tables('table1')",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.TableName != "table1" {
+ t.Fatalf("Expected Table Name to be `table1` but got %q", actual.TableName)
+ }
+ }
+}
diff --git a/storage/2018-03-28/table/tables/version.go b/storage/2018-03-28/table/tables/version.go
new file mode 100644
index 0000000..83a36d2
--- /dev/null
+++ b/storage/2018-03-28/table/tables/version.go
@@ -0,0 +1,14 @@
+package tables
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2018-03-28"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2018-11-09/README.md b/storage/2018-11-09/README.md
new file mode 100644
index 0000000..01dbd7f
--- /dev/null
+++ b/storage/2018-11-09/README.md
@@ -0,0 +1,25 @@
+# Storage API Version 2018-11-09
+
+The following API's are supported by this SDK - more information about each SDK can be found within the README in each package.
+
+## Blob Storage
+
+- [Blobs API](blob/blobs)
+- [Containers API](blob/containers)
+
+## File Storage
+
+- [Directories API](file/directories)
+- [Files API](file/files)
+- [Shares API](file/shares)
+
+## Queue Storage
+
+- [Queues API](queue/queues)
+- [Messages API](queue/messages)
+
+## Table Storage
+
+- [Entities API](table/entities)
+- [Tables API](table/tables)
+
diff --git a/storage/2018-11-09/blob/blobs/README.md b/storage/2018-11-09/blob/blobs/README.md
new file mode 100644
index 0000000..c246993
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/README.md
@@ -0,0 +1,46 @@
+## Blob Storage Blobs SDK for API version 2018-11-09
+
+This package allows you to interact with the Blobs Blob Storage API
+
+### Supported Authorizers
+
+* Azure Active Directory (for the Resource Endpoint `https://storage.azure.com`)
+* SharedKeyLite (Blob, File & Queue)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/blobs"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ containerName := "mycontainer"
+ fileName := "example-large-file.iso"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ blobClient := blobs.New()
+ blobClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ copyInput := blobs.CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+ refreshInterval := 5 * time.Second
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, fileName, copyInput, refreshInterval); err != nil {
+ return fmt.Errorf("Error copying: %s", err)
+ }
+
+ return nil
+}
+
+```
\ No newline at end of file
diff --git a/storage/2018-11-09/blob/blobs/append_block.go b/storage/2018-11-09/blob/blobs/append_block.go
new file mode 100644
index 0000000..7fed86a
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/append_block.go
@@ -0,0 +1,170 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type AppendBlockInput struct {
+
+ // A number indicating the byte offset to compare.
+ // Append Block will succeed only if the append position is equal to this number.
+ // If it is not, the request will fail with an AppendPositionConditionNotMet
+ // error (HTTP status code 412 – Precondition Failed)
+ BlobConditionAppendPosition *int64
+
+ // The max length in bytes permitted for the append blob.
+ // If the Append Block operation would cause the blob to exceed that limit or if the blob size
+ // is already greater than the value specified in this header, the request will fail with
+ // an MaxBlobSizeConditionNotMet error (HTTP status code 412 – Precondition Failed).
+ BlobConditionMaxSize *int64
+
+ // The Bytes which should be appended to the end of this Append Blob.
+ Content []byte
+
+ // An MD5 hash of the block content.
+ // This hash is used to verify the integrity of the block during transport.
+ // When this header is specified, the storage service compares the hash of the content
+ // that has arrived with this header value.
+ //
+ // Note that this MD5 hash is not stored with the blob.
+ // If the two hashes do not match, the operation will fail with error code 400 (Bad Request).
+ ContentMD5 *string
+
+ // Required if the blob has an active lease.
+ // To perform this operation on a blob with an active lease, specify the valid lease ID for this header.
+ LeaseID *string
+}
+
+type AppendBlockResult struct {
+ autorest.Response
+
+ BlobAppendOffset string
+ BlobCommittedBlockCount int64
+ ContentMD5 string
+ ETag string
+ LastModified string
+}
+
+// AppendBlock commits a new block of data to the end of an existing append blob.
+func (client Client) AppendBlock(ctx context.Context, accountName, containerName, blobName string, input AppendBlockInput) (result AppendBlockResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "AppendBlock", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "AppendBlock", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "AppendBlock", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "AppendBlock", "`blobName` cannot be an empty string.")
+ }
+ if len(input.Content) > (4 * 1024 * 1024) {
+ return result, validation.NewError("files.Client", "PutByteRange", "`input.Content` must be at most 4MB.")
+ }
+
+ req, err := client.AppendBlockPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AppendBlock", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.AppendBlockSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AppendBlock", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.AppendBlockResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AppendBlock", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// AppendBlockPreparer prepares the AppendBlock request.
+func (client Client) AppendBlockPreparer(ctx context.Context, accountName, containerName, blobName string, input AppendBlockInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "appendblock"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.BlobConditionAppendPosition != nil {
+ headers["x-ms-blob-condition-appendpos"] = *input.BlobConditionAppendPosition
+ }
+ if input.BlobConditionMaxSize != nil {
+ headers["x-ms-blob-condition-maxsize"] = *input.BlobConditionMaxSize
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithBytes(&input.Content))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// AppendBlockSender sends the AppendBlock request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) AppendBlockSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// AppendBlockResponder handles the response to the AppendBlock request. The method always
+// closes the http.Response Body.
+func (client Client) AppendBlockResponder(resp *http.Response) (result AppendBlockResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.BlobAppendOffset = resp.Header.Get("x-ms-blob-append-offset")
+ result.ContentMD5 = resp.Header.Get("ETag")
+ result.ETag = resp.Header.Get("ETag")
+ result.LastModified = resp.Header.Get("Last-Modified")
+
+ if v := resp.Header.Get("x-ms-blob-committed-block-count"); v != "" {
+ i, innerErr := strconv.Atoi(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as an integer: %s", v, innerErr)
+ return
+ }
+
+ result.BlobCommittedBlockCount = int64(i)
+ }
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/blob_append_test.go b/storage/2018-11-09/blob/blobs/blob_append_test.go
new file mode 100644
index 0000000..6eb34a0
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/blob_append_test.go
@@ -0,0 +1,155 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestAppendBlobLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "append-blob.txt"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithStorageResourceManagerAuth(containersClient.Client)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Putting Append Blob..")
+ if _, err := blobClient.PutAppendBlob(ctx, accountName, containerName, fileName, PutAppendBlobInput{}); err != nil {
+ t.Fatalf("Error putting append blob: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Properties..")
+ props, err := blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error retrieving properties: %s", err)
+ }
+ if props.ContentLength != 0 {
+ t.Fatalf("Expected Content-Length to be 0 but it was %d", props.ContentLength)
+ }
+
+ t.Logf("[DEBUG] Appending First Block..")
+ appendInput := AppendBlockInput{
+ Content: []byte{
+ 12,
+ 48,
+ 93,
+ 76,
+ 29,
+ 10,
+ },
+ }
+ if _, err := blobClient.AppendBlock(ctx, accountName, containerName, fileName, appendInput); err != nil {
+ t.Fatalf("Error appending first block: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-Retrieving Properties..")
+ props, err = blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error retrieving properties: %s", err)
+ }
+ if props.ContentLength != 6 {
+ t.Fatalf("Expected Content-Length to be 6 but it was %d", props.ContentLength)
+ }
+
+ t.Logf("[DEBUG] Appending Second Block..")
+ appendInput = AppendBlockInput{
+ Content: []byte{
+ 92,
+ 62,
+ 64,
+ 47,
+ 83,
+ 77,
+ },
+ }
+ if _, err := blobClient.AppendBlock(ctx, accountName, containerName, fileName, appendInput); err != nil {
+ t.Fatalf("Error appending Second block: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-Retrieving Properties..")
+ props, err = blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error retrieving properties: %s", err)
+ }
+ if props.ContentLength != 12 {
+ t.Fatalf("Expected Content-Length to be 12 but it was %d", props.ContentLength)
+ }
+
+ t.Logf("[DEBUG] Acquiring Lease..")
+ leaseDetails, err := blobClient.AcquireLease(ctx, accountName, containerName, fileName, AcquireLeaseInput{
+ LeaseDuration: -1,
+ })
+ if err != nil {
+ t.Fatalf("Error acquiring Lease: %s", err)
+ }
+ t.Logf("[DEBUG] Lease ID is %q", leaseDetails.LeaseID)
+
+ t.Logf("[DEBUG] Appending Third Block..")
+ appendInput = AppendBlockInput{
+ Content: []byte{
+ 64,
+ 35,
+ 28,
+ 93,
+ 11,
+ 23,
+ },
+ LeaseID: &leaseDetails.LeaseID,
+ }
+ if _, err := blobClient.AppendBlock(ctx, accountName, containerName, fileName, appendInput); err != nil {
+ t.Fatalf("Error appending Third block: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-Retrieving Properties..")
+ props, err = blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{
+ LeaseID: &leaseDetails.LeaseID,
+ })
+ if err != nil {
+ t.Fatalf("Error retrieving properties: %s", err)
+ }
+ if props.ContentLength != 18 {
+ t.Fatalf("Expected Content-Length to be 18 but it was %d", props.ContentLength)
+ }
+
+ t.Logf("[DEBUG] Breaking Lease..")
+ breakLeaseInput := BreakLeaseInput{
+ LeaseID: leaseDetails.LeaseID,
+ }
+ if _, err := blobClient.BreakLease(ctx, accountName, containerName, fileName, breakLeaseInput); err != nil {
+ t.Fatalf("Error breaking lease: %s", err)
+ }
+
+ t.Logf("[DEBUG] Deleting Lease..")
+ if _, err := blobClient.Delete(ctx, accountName, containerName, fileName, DeleteInput{}); err != nil {
+ t.Fatalf("Error deleting: %s", err)
+ }
+}
diff --git a/storage/2018-11-09/blob/blobs/blob_page_test.go b/storage/2018-11-09/blob/blobs/blob_page_test.go
new file mode 100644
index 0000000..6b1efa9
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/blob_page_test.go
@@ -0,0 +1,89 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestPageBlobLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "append-blob.txt"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.StorageV2)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithStorageResourceManagerAuth(containersClient.Client)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Putting Page Blob..")
+ fileSize := int64(10240000)
+ if _, err := blobClient.PutPageBlob(ctx, accountName, containerName, fileName, PutPageBlobInput{
+ BlobContentLengthBytes: fileSize,
+ }); err != nil {
+ t.Fatalf("Error putting page blob: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Properties..")
+ props, err := blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error retrieving properties: %s", err)
+ }
+ if props.ContentLength != fileSize {
+ t.Fatalf("Expected Content-Length to be %d but it was %d", fileSize, props.ContentLength)
+ }
+
+ for iteration := 1; iteration <= 3; iteration++ {
+ t.Logf("[DEBUG] Putting Page %d of 3..", iteration)
+ byteArray := func() []byte {
+ o := make([]byte, 0)
+
+ for i := 0; i < 512; i++ {
+ o = append(o, byte(i))
+ }
+
+ return o
+ }()
+ startByte := int64(512 * iteration)
+ endByte := int64(startByte + 511)
+ putPageInput := PutPageUpdateInput{
+ StartByte: startByte,
+ EndByte: endByte,
+ Content: byteArray,
+ }
+ if _, err := blobClient.PutPageUpdate(ctx, accountName, containerName, fileName, putPageInput); err != nil {
+ t.Fatalf("Error putting page: %s", err)
+ }
+ }
+
+ t.Logf("[DEBUG] Deleting..")
+ if _, err := blobClient.Delete(ctx, accountName, containerName, fileName, DeleteInput{}); err != nil {
+ t.Fatalf("Error deleting: %s", err)
+ }
+}
diff --git a/storage/2018-11-09/blob/blobs/client.go b/storage/2018-11-09/blob/blobs/client.go
new file mode 100644
index 0000000..db20391
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/client.go
@@ -0,0 +1,25 @@
+package blobs
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for Blob Storage Blobs.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithBaseURI creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2018-11-09/blob/blobs/copy.go b/storage/2018-11-09/blob/blobs/copy.go
new file mode 100644
index 0000000..febaab5
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/copy.go
@@ -0,0 +1,235 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type CopyInput struct {
+ // Specifies the name of the source blob or file.
+ // Beginning with version 2012-02-12, this value may be a URL of up to 2 KB in length that specifies a blob.
+ // The value should be URL-encoded as it would appear in a request URI.
+ // A source blob in the same storage account can be authenticated via Shared Key.
+ // However, if the source is a blob in another account,
+ // the source blob must either be public or must be authenticated via a shared access signature.
+ // If the source blob is public, no authentication is required to perform the copy operation.
+ //
+ // Beginning with version 2015-02-21, the source object may be a file in the Azure File service.
+ // If the source object is a file that is to be copied to a blob, then the source file must be authenticated
+ // using a shared access signature, whether it resides in the same account or in a different account.
+ //
+ // Only storage accounts created on or after June 7th, 2012 allow the Copy Blob operation to
+ // copy from another storage account.
+ CopySource string
+
+ // The ID of the Lease
+ // Required if the destination blob has an active lease.
+ // The lease ID specified for this header must match the lease ID of the destination blob.
+ // If the request does not include the lease ID or it is not valid,
+ // the operation fails with status code 412 (Precondition Failed).
+ //
+ // If this header is specified and the destination blob does not currently have an active lease,
+ // the operation will also fail with status code 412 (Precondition Failed).
+ LeaseID *string
+
+ // The ID of the Lease on the Source Blob
+ // Specify to perform the Copy Blob operation only if the lease ID matches the active lease ID of the source blob.
+ SourceLeaseID *string
+
+ // For page blobs on a premium account only. Specifies the tier to be set on the target blob
+ AccessTier *AccessTier
+
+ // A user-defined name-value pair associated with the blob.
+ // If no name-value pairs are specified, the operation will copy the metadata from the source blob or
+ // file to the destination blob.
+ // If one or more name-value pairs are specified, the destination blob is created with the specified metadata,
+ // and metadata is not copied from the source blob or file.
+ MetaData map[string]string
+
+ // An ETag value.
+ // Specify an ETag value for this conditional header to copy the blob only if the specified
+ // ETag value matches the ETag value for an existing destination blob.
+ // If the ETag for the destination blob does not match the ETag specified for If-Match,
+ // the Blob service returns status code 412 (Precondition Failed).
+ IfMatch *string
+
+ // An ETag value, or the wildcard character (*).
+ // Specify an ETag value for this conditional header to copy the blob only if the specified
+ // ETag value does not match the ETag value for the destination blob.
+ // Specify the wildcard character (*) to perform the operation only if the destination blob does not exist.
+ // If the specified condition isn't met, the Blob service returns status code 412 (Precondition Failed).
+ IfNoneMatch *string
+
+ // A DateTime value.
+ // Specify this conditional header to copy the blob only if the destination blob
+ // has been modified since the specified date/time.
+ // If the destination blob has not been modified, the Blob service returns status code 412 (Precondition Failed).
+ IfModifiedSince *string
+
+ // A DateTime value.
+ // Specify this conditional header to copy the blob only if the destination blob
+ // has not been modified since the specified date/time.
+ // If the destination blob has been modified, the Blob service returns status code 412 (Precondition Failed).
+ IfUnmodifiedSince *string
+
+ // An ETag value.
+ // Specify this conditional header to copy the source blob only if its ETag matches the value specified.
+ // If the ETag values do not match, the Blob service returns status code 412 (Precondition Failed).
+ // This cannot be specified if the source is an Azure File.
+ SourceIfMatch *string
+
+ // An ETag value.
+ // Specify this conditional header to copy the blob only if its ETag does not match the value specified.
+ // If the values are identical, the Blob service returns status code 412 (Precondition Failed).
+ // This cannot be specified if the source is an Azure File.
+ SourceIfNoneMatch *string
+
+ // A DateTime value.
+ // Specify this conditional header to copy the blob only if the source blob has been modified
+ // since the specified date/time.
+ // If the source blob has not been modified, the Blob service returns status code 412 (Precondition Failed).
+ // This cannot be specified if the source is an Azure File.
+ SourceIfModifiedSince *string
+
+ // A DateTime value.
+ // Specify this conditional header to copy the blob only if the source blob has not been modified
+ // since the specified date/time.
+ // If the source blob has been modified, the Blob service returns status code 412 (Precondition Failed).
+ // This header cannot be specified if the source is an Azure File.
+ SourceIfUnmodifiedSince *string
+}
+
+type CopyResult struct {
+ autorest.Response
+
+ CopyID string
+ CopyStatus string
+}
+
+// Copy copies a blob to a destination within the storage account asynchronously.
+func (client Client) Copy(ctx context.Context, accountName, containerName, blobName string, input CopyInput) (result CopyResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "Copy", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "Copy", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "Copy", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "Copy", "`blobName` cannot be an empty string.")
+ }
+ if input.CopySource == "" {
+ return result, validation.NewError("blobs.Client", "Copy", "`input.CopySource` cannot be an empty string.")
+ }
+
+ req, err := client.CopyPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Copy", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CopySender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Copy", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CopyResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Copy", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CopyPreparer prepares the Copy request.
+func (client Client) CopyPreparer(ctx context.Context, accountName, containerName, blobName string, input CopyInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-copy-source": autorest.Encode("header", input.CopySource),
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+ if input.SourceLeaseID != nil {
+ headers["x-ms-source-lease-id"] = *input.SourceLeaseID
+ }
+ if input.AccessTier != nil {
+ headers["x-ms-access-tier"] = string(*input.AccessTier)
+ }
+
+ if input.IfMatch != nil {
+ headers["If-Match"] = *input.IfMatch
+ }
+ if input.IfNoneMatch != nil {
+ headers["If-None-Match"] = *input.IfNoneMatch
+ }
+ if input.IfUnmodifiedSince != nil {
+ headers["If-Unmodified-Since"] = *input.IfUnmodifiedSince
+ }
+ if input.IfModifiedSince != nil {
+ headers["If-Modified-Since"] = *input.IfModifiedSince
+ }
+
+ if input.SourceIfMatch != nil {
+ headers["x-ms-source-if-match"] = *input.SourceIfMatch
+ }
+ if input.SourceIfNoneMatch != nil {
+ headers["x-ms-source-if-none-match"] = *input.SourceIfNoneMatch
+ }
+ if input.SourceIfModifiedSince != nil {
+ headers["x-ms-source-if-modified-since"] = *input.SourceIfModifiedSince
+ }
+ if input.SourceIfUnmodifiedSince != nil {
+ headers["x-ms-source-if-unmodified-since"] = *input.SourceIfUnmodifiedSince
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CopySender sends the Copy request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CopySender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CopyResponder handles the response to the Copy request. The method always
+// closes the http.Response Body.
+func (client Client) CopyResponder(resp *http.Response) (result CopyResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.CopyID = resp.Header.Get("x-ms-copy-id")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/copy_abort.go b/storage/2018-11-09/blob/blobs/copy_abort.go
new file mode 100644
index 0000000..a992ff1
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/copy_abort.go
@@ -0,0 +1,110 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type AbortCopyInput struct {
+ // The Copy ID which should be aborted
+ CopyID string
+
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+}
+
+// AbortCopy aborts a pending Copy Blob operation, and leaves a destination blob with zero length and full metadata.
+func (client Client) AbortCopy(ctx context.Context, accountName, containerName, blobName string, input AbortCopyInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "AbortCopy", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "AbortCopy", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "AbortCopy", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "AbortCopy", "`blobName` cannot be an empty string.")
+ }
+ if input.CopyID == "" {
+ return result, validation.NewError("blobs.Client", "AbortCopy", "`input.CopyID` cannot be an empty string.")
+ }
+
+ req, err := client.AbortCopyPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AbortCopy", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.AbortCopySender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AbortCopy", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.AbortCopyResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AbortCopy", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// AbortCopyPreparer prepares the AbortCopy request.
+func (client Client) AbortCopyPreparer(ctx context.Context, accountName, containerName, blobName string, input AbortCopyInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "copy"),
+ "copyid": autorest.Encode("query", input.CopyID),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-copy-action": "abort",
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// AbortCopySender sends the AbortCopy request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) AbortCopySender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// AbortCopyResponder handles the response to the AbortCopy request. The method always
+// closes the http.Response Body.
+func (client Client) AbortCopyResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/copy_and_wait.go b/storage/2018-11-09/blob/blobs/copy_and_wait.go
new file mode 100644
index 0000000..a1e7fa4
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/copy_and_wait.go
@@ -0,0 +1,41 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "time"
+)
+
+// CopyAndWait copies a blob to a destination within the storage account and waits for it to finish copying.
+func (client Client) CopyAndWait(ctx context.Context, accountName, containerName, blobName string, input CopyInput, pollingInterval time.Duration) error {
+ if _, err := client.Copy(ctx, accountName, containerName, blobName, input); err != nil {
+ return fmt.Errorf("Error copying: %s", err)
+ }
+
+ for true {
+ getInput := GetPropertiesInput{
+ LeaseID: input.LeaseID,
+ }
+ getResult, err := client.GetProperties(ctx, accountName, containerName, blobName, getInput)
+ if err != nil {
+ return fmt.Errorf("")
+ }
+
+ switch getResult.CopyStatus {
+ case Aborted:
+ return fmt.Errorf("Copy was aborted: %s", getResult.CopyStatusDescription)
+
+ case Failed:
+ return fmt.Errorf("Copy failed: %s", getResult.CopyStatusDescription)
+
+ case Success:
+ return nil
+
+ case Pending:
+ time.Sleep(pollingInterval)
+ continue
+ }
+ }
+
+ return fmt.Errorf("Unexpected error waiting for the copy to complete")
+}
diff --git a/storage/2018-11-09/blob/blobs/copy_test.go b/storage/2018-11-09/blob/blobs/copy_test.go
new file mode 100644
index 0000000..4c2a7d7
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/copy_test.go
@@ -0,0 +1,148 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestCopyFromExistingFile(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "ubuntu.iso"
+ copiedFileName := "copied.iso"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithStorageResourceManagerAuth(containersClient.Client)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Copying file to Blob Storage..")
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+
+ refreshInterval := 5 * time.Second
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, fileName, copyInput, refreshInterval); err != nil {
+ t.Fatalf("Error copying: %s", err)
+ }
+
+ t.Logf("[DEBUG] Duplicating that file..")
+ copiedInput := CopyInput{
+ CopySource: fmt.Sprintf("%s/%s/%s", endpoints.GetBlobEndpoint(blobClient.BaseURI, accountName), containerName, fileName),
+ }
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, copiedFileName, copiedInput, refreshInterval); err != nil {
+ t.Fatalf("Error duplicating file: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Properties for the Original File..")
+ props, err := blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error getting properties for the original file: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Properties for the Copied File..")
+ copiedProps, err := blobClient.GetProperties(ctx, accountName, containerName, copiedFileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error getting properties for the copied file: %s", err)
+ }
+
+ if props.ContentLength != copiedProps.ContentLength {
+ t.Fatalf("Expected the content length to be %d but it was %d", props.ContentLength, copiedProps.ContentLength)
+ }
+
+ t.Logf("[DEBUG] Deleting copied file..")
+ if _, err := blobClient.Delete(ctx, accountName, containerName, copiedFileName, DeleteInput{}); err != nil {
+ t.Fatalf("Error deleting file: %s", err)
+ }
+
+ t.Logf("[DEBUG] Deleting original file..")
+ if _, err := blobClient.Delete(ctx, accountName, containerName, fileName, DeleteInput{}); err != nil {
+ t.Fatalf("Error deleting file: %s", err)
+ }
+}
+
+func TestCopyFromURL(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "ubuntu.iso"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithStorageResourceManagerAuth(containersClient.Client)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Copying file to Blob Storage..")
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+
+ refreshInterval := 5 * time.Second
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, fileName, copyInput, refreshInterval); err != nil {
+ t.Fatalf("Error copying: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Properties..")
+ props, err := blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error getting properties: %s", err)
+ }
+
+ if props.ContentLength == 0 {
+ t.Fatalf("Expected the file to be there but looks like it isn't: %d", props.ContentLength)
+ }
+
+ t.Logf("[DEBUG] Deleting file..")
+ if _, err := blobClient.Delete(ctx, accountName, containerName, fileName, DeleteInput{}); err != nil {
+ t.Fatalf("Error deleting file: %s", err)
+ }
+}
diff --git a/storage/2018-11-09/blob/blobs/delete.go b/storage/2018-11-09/blob/blobs/delete.go
new file mode 100644
index 0000000..c1c642d
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/delete.go
@@ -0,0 +1,105 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type DeleteInput struct {
+ // Should any Snapshots for this Blob also be deleted?
+ // If the Blob has Snapshots and this is set to False a 409 Conflict will be returned
+ DeleteSnapshots bool
+
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+}
+
+// Delete marks the specified blob or snapshot for deletion. The blob is later deleted during garbage collection.
+func (client Client) Delete(ctx context.Context, accountName, containerName, blobName string, input DeleteInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "Delete", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "Delete", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "Delete", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, containerName, blobName string, input DeleteInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ if input.DeleteSnapshots {
+ headers["x-ms-delete-snapshots"] = "include"
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/delete_snapshot.go b/storage/2018-11-09/blob/blobs/delete_snapshot.go
new file mode 100644
index 0000000..18c3d4c
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/delete_snapshot.go
@@ -0,0 +1,108 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type DeleteSnapshotInput struct {
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+
+ // The DateTime of the Snapshot which should be marked for Deletion
+ SnapshotDateTime string
+}
+
+// DeleteSnapshot marks a single Snapshot of a Blob for Deletion based on it's DateTime, which will be deleted during the next Garbage Collection cycle.
+func (client Client) DeleteSnapshot(ctx context.Context, accountName, containerName, blobName string, input DeleteSnapshotInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshot", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshot", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshot", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshot", "`blobName` cannot be an empty string.")
+ }
+ if input.SnapshotDateTime == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshot", "`input.SnapshotDateTime` cannot be an empty string.")
+ }
+
+ req, err := client.DeleteSnapshotPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "DeleteSnapshot", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSnapshotSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "DeleteSnapshot", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteSnapshotResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "DeleteSnapshot", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeleteSnapshotPreparer prepares the DeleteSnapshot request.
+func (client Client) DeleteSnapshotPreparer(ctx context.Context, accountName, containerName, blobName string, input DeleteSnapshotInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "snapshot": autorest.Encode("query", input.SnapshotDateTime),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSnapshotSender sends the DeleteSnapshot request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSnapshotSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteSnapshotResponder handles the response to the DeleteSnapshot request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteSnapshotResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/delete_snapshots.go b/storage/2018-11-09/blob/blobs/delete_snapshots.go
new file mode 100644
index 0000000..e7e2b66
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/delete_snapshots.go
@@ -0,0 +1,99 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type DeleteSnapshotsInput struct {
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+}
+
+// DeleteSnapshots marks all Snapshots of a Blob for Deletion, which will be deleted during the next Garbage Collection Cycle.
+func (client Client) DeleteSnapshots(ctx context.Context, accountName, containerName, blobName string, input DeleteSnapshotsInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshots", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshots", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshots", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "DeleteSnapshots", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.DeleteSnapshotsPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "DeleteSnapshots", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSnapshotsSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "DeleteSnapshots", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteSnapshotsResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "DeleteSnapshots", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeleteSnapshotsPreparer prepares the DeleteSnapshots request.
+func (client Client) DeleteSnapshotsPreparer(ctx context.Context, accountName, containerName, blobName string, input DeleteSnapshotsInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ // only delete the snapshots but leave the blob as-is
+ "x-ms-delete-snapshots": "only",
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSnapshotsSender sends the DeleteSnapshots request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSnapshotsSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteSnapshotsResponder handles the response to the DeleteSnapshots request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteSnapshotsResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/get.go b/storage/2018-11-09/blob/blobs/get.go
new file mode 100644
index 0000000..fa88081
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/get.go
@@ -0,0 +1,116 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetInput struct {
+ LeaseID *string
+ StartByte *int64
+ EndByte *int64
+}
+
+type GetResult struct {
+ autorest.Response
+
+ Contents []byte
+}
+
+// Get reads or downloads a blob from the system, including its metadata and properties.
+func (client Client) Get(ctx context.Context, accountName, containerName, blobName string, input GetInput) (result GetResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "Get", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "Get", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "Get", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "Get", "`blobName` cannot be an empty string.")
+ }
+ if input.LeaseID != nil && *input.LeaseID == "" {
+ return result, validation.NewError("blobs.Client", "Get", "`input.LeaseID` should either be specified or nil, not an empty string.")
+ }
+ if (input.StartByte != nil && input.EndByte == nil) || input.StartByte == nil && input.EndByte != nil {
+ return result, validation.NewError("blobs.Client", "Get", "`input.StartByte` and `input.EndByte` must both be specified, or both be nil.")
+ }
+
+ req, err := client.GetPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Get", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Get", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Get", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPreparer prepares the Get request.
+func (client Client) GetPreparer(ctx context.Context, accountName, containerName, blobName string, input GetInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.StartByte != nil && input.EndByte != nil {
+ headers["x-ms-range"] = fmt.Sprintf("bytes=%d-%d", *input.StartByte, *input.EndByte)
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetSender sends the Get request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetResponder handles the response to the Get request. The method always
+// closes the http.Response Body.
+func (client Client) GetResponder(resp *http.Response) (result GetResult, err error) {
+ if resp != nil {
+ result.Contents = make([]byte, resp.ContentLength)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusPartialContent),
+ autorest.ByUnmarshallingBytes(&result.Contents),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/get_block_list.go b/storage/2018-11-09/blob/blobs/get_block_list.go
new file mode 100644
index 0000000..9f8120c
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/get_block_list.go
@@ -0,0 +1,140 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetBlockListInput struct {
+ BlockListType BlockListType
+ LeaseID *string
+}
+
+type GetBlockListResult struct {
+ autorest.Response
+
+ // The size of the blob in bytes
+ ContentLength *int64
+
+ // The Content Type of the blob
+ ContentType string
+
+ // The ETag associated with this blob
+ ETag string
+
+ // A list of blocks which have been committed
+ CommittedBlocks CommittedBlocks `xml:"CommittedBlocks,omitempty"`
+
+ // A list of blocks which have not yet been committed
+ UncommittedBlocks UncommittedBlocks `xml:"UncommittedBlocks,omitempty"`
+}
+
+// GetBlockList retrieves the list of blocks that have been uploaded as part of a block blob.
+func (client Client) GetBlockList(ctx context.Context, accountName, containerName, blobName string, input GetBlockListInput) (result GetBlockListResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "GetBlockList", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "GetBlockList", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "GetBlockList", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "GetBlockList", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.GetBlockListPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetBlockList", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetBlockListSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetBlockList", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetBlockListResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetBlockList", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetBlockListPreparer prepares the GetBlockList request.
+func (client Client) GetBlockListPreparer(ctx context.Context, accountName, containerName, blobName string, input GetBlockListInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "blocklisttype": autorest.Encode("query", string(input.BlockListType)),
+ "comp": autorest.Encode("query", "blocklist"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetBlockListSender sends the GetBlockList request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetBlockListSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetBlockListResponder handles the response to the GetBlockList request. The method always
+// closes the http.Response Body.
+func (client Client) GetBlockListResponder(resp *http.Response) (result GetBlockListResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.ContentType = resp.Header.Get("Content-Type")
+ result.ETag = resp.Header.Get("ETag")
+
+ if v := resp.Header.Get("x-ms-blob-content-length"); v != "" {
+ i, innerErr := strconv.Atoi(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as an integer: %s", v, innerErr)
+ return
+ }
+
+ i64 := int64(i)
+ result.ContentLength = &i64
+ }
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/get_page_ranges.go b/storage/2018-11-09/blob/blobs/get_page_ranges.go
new file mode 100644
index 0000000..37abf63
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/get_page_ranges.go
@@ -0,0 +1,152 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetPageRangesInput struct {
+ LeaseID *string
+
+ StartByte *int64
+ EndByte *int64
+}
+
+type GetPageRangesResult struct {
+ autorest.Response
+
+ // The size of the blob in bytes
+ ContentLength *int64
+
+ // The Content Type of the blob
+ ContentType string
+
+ // The ETag associated with this blob
+ ETag string
+
+ PageRanges []PageRange `xml:"PageRange"`
+}
+
+type PageRange struct {
+ // The start byte offset for this range, inclusive
+ Start int64 `xml:"Start"`
+
+ // The end byte offset for this range, inclusive
+ End int64 `xml:"End"`
+}
+
+// GetPageRanges returns the list of valid page ranges for a page blob or snapshot of a page blob.
+func (client Client) GetPageRanges(ctx context.Context, accountName, containerName, blobName string, input GetPageRangesInput) (result GetPageRangesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "GetPageRanges", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "GetPageRanges", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "GetPageRanges", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "GetPageRanges", "`blobName` cannot be an empty string.")
+ }
+ if (input.StartByte != nil && input.EndByte == nil) || input.StartByte == nil && input.EndByte != nil {
+ return result, validation.NewError("blobs.Client", "GetPageRanges", "`input.StartByte` and `input.EndByte` must both be specified, or both be nil.")
+ }
+
+ req, err := client.GetPageRangesPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetPageRanges", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetPageRangesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetPageRanges", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetPageRangesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetPageRanges", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPageRangesPreparer prepares the GetPageRanges request.
+func (client Client) GetPageRangesPreparer(ctx context.Context, accountName, containerName, blobName string, input GetPageRangesInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "pagelist"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ if input.StartByte != nil && input.EndByte != nil {
+ headers["x-ms-range"] = fmt.Sprintf("bytes=%d-%d", *input.StartByte, *input.EndByte)
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetPageRangesSender sends the GetPageRanges request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetPageRangesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetPageRangesResponder handles the response to the GetPageRanges request. The method always
+// closes the http.Response Body.
+func (client Client) GetPageRangesResponder(resp *http.Response) (result GetPageRangesResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.ContentType = resp.Header.Get("Content-Type")
+ result.ETag = resp.Header.Get("ETag")
+
+ if v := resp.Header.Get("x-ms-blob-content-length"); v != "" {
+ i, innerErr := strconv.Atoi(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as an integer: %s", v, innerErr)
+ return
+ }
+
+ i64 := int64(i)
+ result.ContentLength = &i64
+ }
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/incremental_copy_blob.go b/storage/2018-11-09/blob/blobs/incremental_copy_blob.go
new file mode 100644
index 0000000..7fb7e6b
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/incremental_copy_blob.go
@@ -0,0 +1,120 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type IncrementalCopyBlobInput struct {
+ CopySource string
+ IfModifiedSince *string
+ IfUnmodifiedSince *string
+ IfMatch *string
+ IfNoneMatch *string
+}
+
+// IncrementalCopyBlob copies a snapshot of the source page blob to a destination page blob.
+// The snapshot is copied such that only the differential changes between the previously copied
+// snapshot are transferred to the destination.
+// The copied snapshots are complete copies of the original snapshot and can be read or copied from as usual.
+func (client Client) IncrementalCopyBlob(ctx context.Context, accountName, containerName, blobName string, input IncrementalCopyBlobInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "IncrementalCopyBlob", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "IncrementalCopyBlob", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "IncrementalCopyBlob", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "IncrementalCopyBlob", "`blobName` cannot be an empty string.")
+ }
+ if input.CopySource == "" {
+ return result, validation.NewError("blobs.Client", "IncrementalCopyBlob", "`input.CopySource` cannot be an empty string.")
+ }
+
+ req, err := client.IncrementalCopyBlobPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "IncrementalCopyBlob", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.IncrementalCopyBlobSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "IncrementalCopyBlob", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.IncrementalCopyBlobResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "IncrementalCopyBlob", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// IncrementalCopyBlobPreparer prepares the IncrementalCopyBlob request.
+func (client Client) IncrementalCopyBlobPreparer(ctx context.Context, accountName, containerName, blobName string, input IncrementalCopyBlobInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "incrementalcopy"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-copy-source": input.CopySource,
+ }
+
+ if input.IfModifiedSince != nil {
+ headers["If-Modified-Since"] = *input.IfModifiedSince
+ }
+ if input.IfUnmodifiedSince != nil {
+ headers["If-Unmodified-Since"] = *input.IfUnmodifiedSince
+ }
+ if input.IfMatch != nil {
+ headers["If-Match"] = *input.IfMatch
+ }
+ if input.IfNoneMatch != nil {
+ headers["If-None-Match"] = *input.IfNoneMatch
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// IncrementalCopyBlobSender sends the IncrementalCopyBlob request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) IncrementalCopyBlobSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// IncrementalCopyBlobResponder handles the response to the IncrementalCopyBlob request. The method always
+// closes the http.Response Body.
+func (client Client) IncrementalCopyBlobResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/lease_acquire.go b/storage/2018-11-09/blob/blobs/lease_acquire.go
new file mode 100644
index 0000000..432c1f5
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/lease_acquire.go
@@ -0,0 +1,135 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type AcquireLeaseInput struct {
+ // The ID of the existing Lease, if leased
+ LeaseID *string
+
+ // Specifies the duration of the lease, in seconds, or negative one (-1) for a lease that never expires.
+ // A non-infinite lease can be between 15 and 60 seconds
+ LeaseDuration int
+
+ // The Proposed new ID for the Lease
+ ProposedLeaseID *string
+}
+
+type AcquireLeaseResult struct {
+ autorest.Response
+
+ LeaseID string
+}
+
+// AcquireLease establishes and manages a lock on a blob for write and delete operations.
+func (client Client) AcquireLease(ctx context.Context, accountName, containerName, blobName string, input AcquireLeaseInput) (result AcquireLeaseResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`blobName` cannot be an empty string.")
+ }
+ if input.LeaseID != nil && *input.LeaseID == "" {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`input.LeaseID` cannot be an empty string, if specified.")
+ }
+ if input.ProposedLeaseID != nil && *input.ProposedLeaseID == "" {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`input.ProposedLeaseID` cannot be an empty string, if specified.")
+ }
+ // An infinite lease duration is -1 seconds. A non-infinite lease can be between 15 and 60 seconds
+ if input.LeaseDuration != -1 && (input.LeaseDuration <= 15 || input.LeaseDuration >= 60) {
+ return result, validation.NewError("blobs.Client", "AcquireLease", "`input.LeaseDuration` must be -1 (infinite), or between 15 and 60 seconds.")
+ }
+
+ req, err := client.AcquireLeasePreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AcquireLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.AcquireLeaseSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AcquireLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.AcquireLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "AcquireLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// AcquireLeasePreparer prepares the AcquireLease request.
+func (client Client) AcquireLeasePreparer(ctx context.Context, accountName, containerName, blobName string, input AcquireLeaseInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "acquire",
+ "x-ms-lease-duration": input.LeaseDuration,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ if input.ProposedLeaseID != nil {
+ headers["x-ms-proposed-lease-id"] = input.ProposedLeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// AcquireLeaseSender sends the AcquireLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) AcquireLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// AcquireLeaseResponder handles the response to the AcquireLease request. The method always
+// closes the http.Response Body.
+func (client Client) AcquireLeaseResponder(resp *http.Response) (result AcquireLeaseResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.LeaseID = resp.Header.Get("x-ms-lease-id")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/lease_break.go b/storage/2018-11-09/blob/blobs/lease_break.go
new file mode 100644
index 0000000..d564204
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/lease_break.go
@@ -0,0 +1,124 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type BreakLeaseInput struct {
+ // For a break operation, proposed duration the lease should continue
+ // before it is broken, in seconds, between 0 and 60.
+ // This break period is only used if it is shorter than the time remaining on the lease.
+ // If longer, the time remaining on the lease is used.
+ // A new lease will not be available before the break period has expired,
+ // but the lease may be held for longer than the break period.
+ // If this header does not appear with a break operation, a fixed-duration lease breaks
+ // after the remaining lease period elapses, and an infinite lease breaks immediately.
+ BreakPeriod *int
+
+ LeaseID string
+}
+
+type BreakLeaseResponse struct {
+ autorest.Response
+
+ // Approximate time remaining in the lease period, in seconds.
+ // If the break is immediate, 0 is returned.
+ LeaseTime int
+}
+
+// BreakLease breaks an existing lock on a blob using the LeaseID.
+func (client Client) BreakLease(ctx context.Context, accountName, containerName, blobName string, input BreakLeaseInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "BreakLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "BreakLease", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "BreakLease", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "BreakLease", "`blobName` cannot be an empty string.")
+ }
+ if input.LeaseID == "" {
+ return result, validation.NewError("blobs.Client", "BreakLease", "`input.LeaseID` cannot be an empty string.")
+ }
+
+ req, err := client.BreakLeasePreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "BreakLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.BreakLeaseSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "BreakLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.BreakLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "BreakLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// BreakLeasePreparer prepares the BreakLease request.
+func (client Client) BreakLeasePreparer(ctx context.Context, accountName, containerName, blobName string, input BreakLeaseInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "break",
+ "x-ms-lease-id": input.LeaseID,
+ }
+
+ if input.BreakPeriod != nil {
+ headers["x-ms-lease-break-period"] = *input.BreakPeriod
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// BreakLeaseSender sends the BreakLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) BreakLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// BreakLeaseResponder handles the response to the BreakLease request. The method always
+// closes the http.Response Body.
+func (client Client) BreakLeaseResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/lease_change.go b/storage/2018-11-09/blob/blobs/lease_change.go
new file mode 100644
index 0000000..c57f9db
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/lease_change.go
@@ -0,0 +1,117 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type ChangeLeaseInput struct {
+ ExistingLeaseID string
+ ProposedLeaseID string
+}
+
+type ChangeLeaseResponse struct {
+ autorest.Response
+
+ LeaseID string
+}
+
+// ChangeLease changes an existing lock on a blob for another lock.
+func (client Client) ChangeLease(ctx context.Context, accountName, containerName, blobName string, input ChangeLeaseInput) (result ChangeLeaseResponse, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "ChangeLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "ChangeLease", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "ChangeLease", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "ChangeLease", "`blobName` cannot be an empty string.")
+ }
+ if input.ExistingLeaseID == "" {
+ return result, validation.NewError("blobs.Client", "ChangeLease", "`input.ExistingLeaseID` cannot be an empty string.")
+ }
+ if input.ProposedLeaseID == "" {
+ return result, validation.NewError("blobs.Client", "ChangeLease", "`input.ProposedLeaseID` cannot be an empty string.")
+ }
+
+ req, err := client.ChangeLeasePreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "ChangeLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ChangeLeaseSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "ChangeLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ChangeLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "ChangeLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ChangeLeasePreparer prepares the ChangeLease request.
+func (client Client) ChangeLeasePreparer(ctx context.Context, accountName, containerName, blobName string, input ChangeLeaseInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "change",
+ "x-ms-lease-id": input.ExistingLeaseID,
+ "x-ms-proposed-lease-id": input.ProposedLeaseID,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ChangeLeaseSender sends the ChangeLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ChangeLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ChangeLeaseResponder handles the response to the ChangeLease request. The method always
+// closes the http.Response Body.
+func (client Client) ChangeLeaseResponder(resp *http.Response) (result ChangeLeaseResponse, err error) {
+ if resp != nil && resp.Header != nil {
+ result.LeaseID = resp.Header.Get("x-ms-lease-id")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/lease_release.go b/storage/2018-11-09/blob/blobs/lease_release.go
new file mode 100644
index 0000000..0226cdf
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/lease_release.go
@@ -0,0 +1,98 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// ReleaseLease releases a lock based on the Lease ID.
+func (client Client) ReleaseLease(ctx context.Context, accountName, containerName, blobName, leaseID string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "ReleaseLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "ReleaseLease", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "ReleaseLease", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "ReleaseLease", "`blobName` cannot be an empty string.")
+ }
+ if leaseID == "" {
+ return result, validation.NewError("blobs.Client", "ReleaseLease", "`leaseID` cannot be an empty string.")
+ }
+
+ req, err := client.ReleaseLeasePreparer(ctx, accountName, containerName, blobName, leaseID)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "ReleaseLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ReleaseLeaseSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "ReleaseLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ReleaseLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "ReleaseLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ReleaseLeasePreparer prepares the ReleaseLease request.
+func (client Client) ReleaseLeasePreparer(ctx context.Context, accountName, containerName, blobName, leaseID string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "release",
+ "x-ms-lease-id": leaseID,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ReleaseLeaseSender sends the ReleaseLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ReleaseLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ReleaseLeaseResponder handles the response to the ReleaseLease request. The method always
+// closes the http.Response Body.
+func (client Client) ReleaseLeaseResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/lease_renew.go b/storage/2018-11-09/blob/blobs/lease_renew.go
new file mode 100644
index 0000000..69c495b
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/lease_renew.go
@@ -0,0 +1,97 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+func (client Client) RenewLease(ctx context.Context, accountName, containerName, blobName, leaseID string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "RenewLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "RenewLease", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "RenewLease", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "RenewLease", "`blobName` cannot be an empty string.")
+ }
+ if leaseID == "" {
+ return result, validation.NewError("blobs.Client", "RenewLease", "`leaseID` cannot be an empty string.")
+ }
+
+ req, err := client.RenewLeasePreparer(ctx, accountName, containerName, blobName, leaseID)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "RenewLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.RenewLeaseSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "RenewLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.RenewLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "RenewLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// RenewLeasePreparer prepares the RenewLease request.
+func (client Client) RenewLeasePreparer(ctx context.Context, accountName, containerName, blobName, leaseID string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "renew",
+ "x-ms-lease-id": leaseID,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// RenewLeaseSender sends the RenewLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) RenewLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// RenewLeaseResponder handles the response to the RenewLease request. The method always
+// closes the http.Response Body.
+func (client Client) RenewLeaseResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/lease_test.go b/storage/2018-11-09/blob/blobs/lease_test.go
new file mode 100644
index 0000000..7600ab8
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/lease_test.go
@@ -0,0 +1,106 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestLeaseLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "ubuntu.iso"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithStorageResourceManagerAuth(containersClient.Client)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Copying file to Blob Storage..")
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+
+ refreshInterval := 5 * time.Second
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, fileName, copyInput, refreshInterval); err != nil {
+ t.Fatalf("Error copying: %s", err)
+ }
+ defer blobClient.Delete(ctx, accountName, containerName, fileName, DeleteInput{})
+
+ // Test begins here
+ t.Logf("[DEBUG] Acquiring Lease..")
+ leaseInput := AcquireLeaseInput{
+ LeaseDuration: -1,
+ }
+ leaseInfo, err := blobClient.AcquireLease(ctx, accountName, containerName, fileName, leaseInput)
+ if err != nil {
+ t.Fatalf("Error acquiring lease: %s", err)
+ }
+ t.Logf("[DEBUG] Lease ID: %q", leaseInfo.LeaseID)
+
+ t.Logf("[DEBUG] Changing Lease..")
+ changeLeaseInput := ChangeLeaseInput{
+ ExistingLeaseID: leaseInfo.LeaseID,
+ ProposedLeaseID: "31f5bb01-cdd9-4166-bcdc-95186076bde0",
+ }
+ changeLeaseResult, err := blobClient.ChangeLease(ctx, accountName, containerName, fileName, changeLeaseInput)
+ if err != nil {
+ t.Fatalf("Error changing lease: %s", err)
+ }
+ t.Logf("[DEBUG] New Lease ID: %q", changeLeaseResult.LeaseID)
+
+ t.Logf("[DEBUG] Releasing Lease..")
+ if _, err := blobClient.ReleaseLease(ctx, accountName, containerName, fileName, changeLeaseResult.LeaseID); err != nil {
+ t.Fatalf("Error releasing lease: %s", err)
+ }
+
+ t.Logf("[DEBUG] Acquiring a new lease..")
+ leaseInput = AcquireLeaseInput{
+ LeaseDuration: 30,
+ }
+ leaseInfo, err = blobClient.AcquireLease(ctx, accountName, containerName, fileName, leaseInput)
+ if err != nil {
+ t.Fatalf("Error acquiring lease: %s", err)
+ }
+ t.Logf("[DEBUG] Lease ID: %q", leaseInfo.LeaseID)
+
+ t.Logf("[DEBUG] Renewing lease..")
+ if _, err := blobClient.RenewLease(ctx, accountName, containerName, fileName, leaseInfo.LeaseID); err != nil {
+ t.Fatalf("Error renewing lease: %s", err)
+ }
+
+ t.Logf("[DEBUG] Breaking lease..")
+ breakLeaseInput := BreakLeaseInput{
+ LeaseID: leaseInfo.LeaseID,
+ }
+ if _, err := blobClient.BreakLease(ctx, accountName, containerName, fileName, breakLeaseInput); err != nil {
+ t.Fatalf("Error breaking lease: %s", err)
+ }
+}
diff --git a/storage/2018-11-09/blob/blobs/lifecycle_test.go b/storage/2018-11-09/blob/blobs/lifecycle_test.go
new file mode 100644
index 0000000..3456978
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/lifecycle_test.go
@@ -0,0 +1,158 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "example.txt"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithAuthorizer(containersClient.Client, storageAuth)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Copying file to Blob Storage..")
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+
+ refreshInterval := 5 * time.Second
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, fileName, copyInput, refreshInterval); err != nil {
+ t.Fatalf("Error copying: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Blob Properties..")
+ details, err := blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error retrieving properties: %s", err)
+ }
+
+ // default value
+ if details.AccessTier != Hot {
+ t.Fatalf("Expected the AccessTier to be %q but got %q", Hot, details.AccessTier)
+ }
+ if details.BlobType != BlockBlob {
+ t.Fatalf("Expected BlobType to be %q but got %q", BlockBlob, details.BlobType)
+ }
+ if len(details.MetaData) != 0 {
+ t.Fatalf("Expected there to be no items of metadata but got %d", len(details.MetaData))
+ }
+
+ t.Logf("[DEBUG] Checking it's returned in the List API..")
+ listInput := containers.ListBlobsInput{}
+ listResult, err := containersClient.ListBlobs(ctx, accountName, containerName, listInput)
+ if err != nil {
+ t.Fatalf("Error listing blobs: %s", err)
+ }
+
+ if len(listResult.Blobs.Blobs) != 1 {
+ t.Fatalf("Expected there to be 1 blob in the container but got %d", len(listResult.Blobs.Blobs))
+ }
+
+ t.Logf("[DEBUG] Setting MetaData..")
+ metaDataInput := SetMetaDataInput{
+ MetaData: map[string]string{
+ "hello": "there",
+ },
+ }
+ if _, err := blobClient.SetMetaData(ctx, accountName, containerName, fileName, metaDataInput); err != nil {
+ t.Fatalf("Error setting MetaData: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-retrieving Blob Properties..")
+ details, err = blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error re-retrieving properties: %s", err)
+ }
+
+ // default value
+ if details.AccessTier != Hot {
+ t.Fatalf("Expected the AccessTier to be %q but got %q", Hot, details.AccessTier)
+ }
+ if details.BlobType != BlockBlob {
+ t.Fatalf("Expected BlobType to be %q but got %q", BlockBlob, details.BlobType)
+ }
+ if len(details.MetaData) != 1 {
+ t.Fatalf("Expected there to be 1 item of metadata but got %d", len(details.MetaData))
+ }
+ if details.MetaData["hello"] != "there" {
+ t.Fatalf("Expected `hello` to be `there` but got %q", details.MetaData["there"])
+ }
+
+ t.Logf("[DEBUG] Retrieving the Block List..")
+ getBlockListInput := GetBlockListInput{
+ BlockListType: All,
+ }
+ blockList, err := blobClient.GetBlockList(ctx, accountName, containerName, fileName, getBlockListInput)
+ if err != nil {
+ t.Fatalf("Error retrieving Block List: %s", err)
+ }
+
+ // since this is a copy from an existing file, all blocks should be present
+ if len(blockList.CommittedBlocks.Blocks) == 0 {
+ t.Fatalf("Expected there to be committed blocks but there weren't!")
+ }
+ if len(blockList.UncommittedBlocks.Blocks) != 0 {
+ t.Fatalf("Expected all blocks to be committed but got %d uncommitted blocks", len(blockList.UncommittedBlocks.Blocks))
+ }
+
+ t.Logf("[DEBUG] Changing the Access Tiers..")
+ tiers := []AccessTier{
+ Hot,
+ Cool,
+ Archive,
+ }
+ for _, tier := range tiers {
+ t.Logf("[DEBUG] Updating the Access Tier to %q..", string(tier))
+ if _, err := blobClient.SetTier(ctx, accountName, containerName, fileName, tier); err != nil {
+ t.Fatalf("Error setting the Access Tier: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-retrieving Blob Properties..")
+ details, err = blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error re-retrieving properties: %s", err)
+ }
+
+ if details.AccessTier != tier {
+ t.Fatalf("Expected the AccessTier to be %q but got %q", tier, details.AccessTier)
+ }
+ }
+
+ t.Logf("[DEBUG] Deleting Blob")
+ if _, err := blobClient.Delete(ctx, accountName, containerName, fileName, DeleteInput{}); err != nil {
+ t.Fatalf("Error deleting Blob: %s", err)
+ }
+}
diff --git a/storage/2018-11-09/blob/blobs/metadata_set.go b/storage/2018-11-09/blob/blobs/metadata_set.go
new file mode 100644
index 0000000..ec69152
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/metadata_set.go
@@ -0,0 +1,113 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type SetMetaDataInput struct {
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+
+ // Any metadata which should be added to this blob
+ MetaData map[string]string
+}
+
+// SetMetaData marks the specified blob or snapshot for deletion. The blob is later deleted during garbage collection.
+func (client Client) SetMetaData(ctx context.Context, accountName, containerName, blobName string, input SetMetaDataInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`blobName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("blobs.Client", "GetProperties", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.SetMetaDataPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetMetaDataSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetMetaDataPreparer prepares the SetMetaData request.
+func (client Client) SetMetaDataPreparer(ctx context.Context, accountName, containerName, blobName string, input SetMetaDataInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetMetaDataSender sends the SetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetMetaDataResponder handles the response to the SetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) SetMetaDataResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/models.go b/storage/2018-11-09/blob/blobs/models.go
new file mode 100644
index 0000000..d7d83aa
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/models.go
@@ -0,0 +1,82 @@
+package blobs
+
+type AccessTier string
+
+var (
+ Archive AccessTier = "Archive"
+ Cool AccessTier = "Cool"
+ Hot AccessTier = "Hot"
+)
+
+type ArchiveStatus string
+
+var (
+ None ArchiveStatus = ""
+ RehydratePendingToCool ArchiveStatus = "rehydrate-pending-to-cool"
+ RehydratePendingToHot ArchiveStatus = "rehydrate-pending-to-hot"
+)
+
+type BlockListType string
+
+var (
+ All BlockListType = "all"
+ Committed BlockListType = "committed"
+ Uncommitted BlockListType = "uncommitted"
+)
+
+type Block struct {
+ // The base64-encoded Block ID
+ Name string `xml:"Name"`
+
+ // The size of the Block in Bytes
+ Size int64 `xml:"Size"`
+}
+
+type BlobType string
+
+var (
+ AppendBlob BlobType = "AppendBlob"
+ BlockBlob BlobType = "BlockBlob"
+ PageBlob BlobType = "PageBlob"
+)
+
+type CommittedBlocks struct {
+ Blocks []Block `xml:"Block"`
+}
+
+type CopyStatus string
+
+var (
+ Aborted CopyStatus = "aborted"
+ Failed CopyStatus = "failed"
+ Pending CopyStatus = "pending"
+ Success CopyStatus = "success"
+)
+
+type LeaseDuration string
+
+var (
+ Fixed LeaseDuration = "fixed"
+ Infinite LeaseDuration = "infinite"
+)
+
+type LeaseState string
+
+var (
+ Available LeaseState = "available"
+ Breaking LeaseState = "breaking"
+ Broken LeaseState = "broken"
+ Expired LeaseState = "expired"
+ Leased LeaseState = "leased"
+)
+
+type LeaseStatus string
+
+var (
+ Locked LeaseStatus = "locked"
+ Unlocked LeaseStatus = "unlocked"
+)
+
+type UncommittedBlocks struct {
+ Blocks []Block `xml:"Block"`
+}
diff --git a/storage/2018-11-09/blob/blobs/properties_get.go b/storage/2018-11-09/blob/blobs/properties_get.go
new file mode 100644
index 0000000..de7c5fc
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/properties_get.go
@@ -0,0 +1,310 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetPropertiesInput struct {
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+}
+
+type GetPropertiesResult struct {
+ autorest.Response
+
+ // The tier of page blob on a premium storage account or tier of block blob on blob storage or general purpose v2 account.
+ AccessTier AccessTier
+
+ // This gives the last time tier was changed on the object.
+ // This header is returned only if tier on block blob was ever set.
+ // The date format follows RFC 1123
+ AccessTierChangeTime string
+
+ // For page blobs on a premium storage account only.
+ // If the access tier is not explicitly set on the blob, the tier is inferred based on its content length
+ // and this header will be returned with true value.
+ // For block blobs on Blob Storage or general purpose v2 account, if the blob does not have the access tier
+ // set then we infer the tier from the storage account properties. This header is set only if the block blob
+ // tier is inferred
+ AccessTierInferred bool
+
+ // For blob storage or general purpose v2 account.
+ // If the blob is being rehydrated and is not complete then this header is returned indicating
+ // that rehydrate is pending and also tells the destination tier
+ ArchiveStatus ArchiveStatus
+
+ // The number of committed blocks present in the blob.
+ // This header is returned only for append blobs.
+ BlobCommittedBlockCount string
+
+ // The current sequence number for a page blob.
+ // This header is not returned for block blobs or append blobs.
+ // This header is not returned for block blobs.
+ BlobSequenceNumber string
+
+ // The blob type.
+ BlobType BlobType
+
+ // If the Cache-Control request header has previously been set for the blob, that value is returned in this header.
+ CacheControl string
+
+ // The Content-Disposition response header field conveys additional information about how to process
+ // the response payload, and also can be used to attach additional metadata.
+ // For example, if set to attachment, it indicates that the user-agent should not display the response,
+ // but instead show a Save As dialog.
+ ContentDisposition string
+
+ // If the Content-Encoding request header has previously been set for the blob,
+ // that value is returned in this header.
+ ContentEncoding string
+
+ // If the Content-Language request header has previously been set for the blob,
+ // that value is returned in this header.
+ ContentLanguage string
+
+ // The size of the blob in bytes.
+ // For a page blob, this header returns the value of the x-ms-blob-content-length header stored with the blob.
+ ContentLength int64
+
+ // The content type specified for the blob.
+ // If no content type was specified, the default content type is `application/octet-stream`.
+ ContentType string
+
+ // If the Content-MD5 header has been set for the blob, this response header is returned so that
+ // the client can check for message content integrity.
+ ContentMD5 string
+
+ // Conclusion time of the last attempted Copy Blob operation where this blob was the destination blob.
+ // This value can specify the time of a completed, aborted, or failed copy attempt.
+ // This header does not appear if a copy is pending, if this blob has never been the
+ // destination in a Copy Blob operation, or if this blob has been modified after a concluded Copy Blob
+ // operation using Set Blob Properties, Put Blob, or Put Block List.
+ CopyCompletionTime string
+
+ // Included if the blob is incremental copy blob or incremental copy snapshot, if x-ms-copy-status is success.
+ // Snapshot time of the last successful incremental copy snapshot for this blob
+ CopyDestinationSnapshot string
+
+ // String identifier for the last attempted Copy Blob operation where this blob was the destination blob.
+ // This header does not appear if this blob has never been the destination in a Copy Blob operation,
+ // or if this blob has been modified after a concluded Copy Blob operation using Set Blob Properties,
+ // Put Blob, or Put Block List.
+ CopyID string
+
+ // Contains the number of bytes copied and the total bytes in the source in the last attempted
+ // Copy Blob operation where this blob was the destination blob.
+ // Can show between 0 and Content-Length bytes copied.
+ // This header does not appear if this blob has never been the destination in a Copy Blob operation,
+ // or if this blob has been modified after a concluded Copy Blob operation using Set Blob Properties,
+ // Put Blob, or Put Block List.
+ CopyProgress string
+
+ // URL up to 2 KB in length that specifies the source blob used in the last attempted Copy Blob operation
+ // where this blob was the destination blob.
+ // This header does not appear if this blob has never been the destination in a Copy Blob operation,
+ // or if this blob has been modified after a concluded Copy Blob operation using Set Blob Properties,
+ // Put Blob, or Put Block List
+ CopySource string
+
+ // State of the copy operation identified by x-ms-copy-id, with these values:
+ // - success: Copy completed successfully.
+ // - pending: Copy is in progress.
+ // Check x-ms-copy-status-description if intermittent, non-fatal errors
+ // impede copy progress but don’t cause failure.
+ // - aborted: Copy was ended by Abort Copy Blob.
+ // - failed: Copy failed. See x-ms- copy-status-description for failure details.
+ // This header does not appear if this blob has never been the destination in a Copy Blob operation,
+ // or if this blob has been modified after a completed Copy Blob operation using Set Blob Properties,
+ // Put Blob, or Put Block List.
+ CopyStatus CopyStatus
+
+ // Describes cause of fatal or non-fatal copy operation failure.
+ // This header does not appear if this blob has never been the destination in a Copy Blob operation,
+ // or if this blob has been modified after a concluded Copy Blob operation using Set Blob Properties,
+ // Put Blob, or Put Block List.
+ CopyStatusDescription string
+
+ // The date/time at which the blob was created. The date format follows RFC 1123
+ CreationTime string
+
+ // The ETag contains a value that you can use to perform operations conditionally
+ ETag string
+
+ // Included if the blob is incremental copy blob.
+ IncrementalCopy bool
+
+ // The date/time that the blob was last modified. The date format follows RFC 1123.
+ LastModified string
+
+ // When a blob is leased, specifies whether the lease is of infinite or fixed duration
+ LeaseDuration LeaseDuration
+
+ // The lease state of the blob
+ LeaseState LeaseState
+
+ LeaseStatus LeaseStatus
+
+ // A set of name-value pairs that correspond to the user-defined metadata associated with this blob
+ MetaData map[string]string
+
+ // Is the Storage Account encrypted using server-side encryption? This should always return true
+ ServerEncrypted bool
+}
+
+// GetProperties returns all user-defined metadata, standard HTTP properties, and system properties for the blob
+func (client Client) GetProperties(ctx context.Context, accountName, containerName, blobName string, input GetPropertiesInput) (result GetPropertiesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "GetProperties", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.GetPropertiesPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetPropertiesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPropertiesPreparer prepares the GetProperties request.
+func (client Client) GetPropertiesPreparer(ctx context.Context, accountName, containerName, blobName string, input GetPropertiesInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsHead(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetPropertiesSender sends the GetProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetPropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetPropertiesResponder handles the response to the GetProperties request. The method always
+// closes the http.Response Body.
+func (client Client) GetPropertiesResponder(resp *http.Response) (result GetPropertiesResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.AccessTier = AccessTier(resp.Header.Get("x-ms-access-tier"))
+ result.AccessTierChangeTime = resp.Header.Get(" x-ms-access-tier-change-time")
+ result.ArchiveStatus = ArchiveStatus(resp.Header.Get(" x-ms-archive-status"))
+ result.BlobCommittedBlockCount = resp.Header.Get("x-ms-blob-committed-block-count")
+ result.BlobSequenceNumber = resp.Header.Get("x-ms-blob-sequence-number")
+ result.BlobType = BlobType(resp.Header.Get("x-ms-blob-type"))
+ result.CacheControl = resp.Header.Get("Cache-Control")
+ result.ContentDisposition = resp.Header.Get("Content-Disposition")
+ result.ContentEncoding = resp.Header.Get("Content-Encoding")
+ result.ContentLanguage = resp.Header.Get("Content-Language")
+ result.ContentMD5 = resp.Header.Get("Content-MD5")
+ result.ContentType = resp.Header.Get("Content-Type")
+ result.CopyCompletionTime = resp.Header.Get("x-ms-copy-completion-time")
+ result.CopyDestinationSnapshot = resp.Header.Get("x-ms-copy-destination-snapshot")
+ result.CopyID = resp.Header.Get("x-ms-copy-id")
+ result.CopyProgress = resp.Header.Get(" x-ms-copy-progress")
+ result.CopySource = resp.Header.Get("x-ms-copy-source")
+ result.CopyStatus = CopyStatus(resp.Header.Get("x-ms-copy-status"))
+ result.CopyStatusDescription = resp.Header.Get("x-ms-copy-status-description")
+ result.CreationTime = resp.Header.Get("x-ms-creation-time")
+ result.ETag = resp.Header.Get("Etag")
+ result.LastModified = resp.Header.Get("Last-Modified")
+ result.LeaseDuration = LeaseDuration(resp.Header.Get("x-ms-lease-duration"))
+ result.LeaseState = LeaseState(resp.Header.Get("x-ms-lease-state"))
+ result.LeaseStatus = LeaseStatus(resp.Header.Get("x-ms-lease-status"))
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+
+ if v := resp.Header.Get("x-ms-access-tier-inferred"); v != "" {
+ b, innerErr := strconv.ParseBool(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as a bool: %s", v, innerErr)
+ return
+ }
+
+ result.AccessTierInferred = b
+ }
+
+ if v := resp.Header.Get("Content-Length"); v != "" {
+ i, innerErr := strconv.Atoi(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as an integer: %s", v, innerErr)
+ }
+
+ result.ContentLength = int64(i)
+ }
+
+ if v := resp.Header.Get("x-ms-incremental-copy"); v != "" {
+ b, innerErr := strconv.ParseBool(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as a bool: %s", v, innerErr)
+ return
+ }
+
+ result.IncrementalCopy = b
+ }
+
+ if v := resp.Header.Get("x-ms-server-encrypted"); v != "" {
+ b, innerErr := strconv.ParseBool(v)
+ if innerErr != nil {
+ err = fmt.Errorf("Error parsing %q as a bool: %s", v, innerErr)
+ return
+ }
+
+ result.IncrementalCopy = b
+ }
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/properties_set.go b/storage/2018-11-09/blob/blobs/properties_set.go
new file mode 100644
index 0000000..a8c0ed8
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/properties_set.go
@@ -0,0 +1,156 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type SetPropertiesInput struct {
+ CacheControl *string
+ ContentType *string
+ ContentMD5 *string
+ ContentEncoding *string
+ ContentLanguage *string
+ LeaseID *string
+ ContentDisposition *string
+ ContentLength *int64
+ SequenceNumberAction *SequenceNumberAction
+ BlobSequenceNumber *string
+}
+
+type SetPropertiesResult struct {
+ autorest.Response
+
+ BlobSequenceNumber string
+ Etag string
+}
+
+// SetProperties sets system properties on the blob.
+func (client Client) SetProperties(ctx context.Context, accountName, containerName, blobName string, input SetPropertiesInput) (result SetPropertiesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "SetProperties", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "SetProperties", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "SetProperties", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "SetProperties", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.SetPropertiesPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetPropertiesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+type SequenceNumberAction string
+
+var (
+ Increment SequenceNumberAction = "increment"
+ Max SequenceNumberAction = "max"
+ Update SequenceNumberAction = "update"
+)
+
+// SetPropertiesPreparer prepares the SetProperties request.
+func (client Client) SetPropertiesPreparer(ctx context.Context, accountName, containerName, blobName string, input SetPropertiesInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "properties"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.CacheControl != nil {
+ headers["x-ms-blob-cache-control"] = *input.CacheControl
+ }
+ if input.ContentDisposition != nil {
+ headers["x-ms-blob-content-disposition"] = *input.ContentDisposition
+ }
+ if input.ContentEncoding != nil {
+ headers["x-ms-blob-content-encoding"] = *input.ContentEncoding
+ }
+ if input.ContentLanguage != nil {
+ headers["x-ms-blob-content-language"] = *input.ContentLanguage
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.ContentType != nil {
+ headers["x-ms-blob-content-type"] = *input.ContentType
+ }
+ if input.ContentLength != nil {
+ headers["x-ms-blob-content-length"] = *input.ContentLength
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+ if input.SequenceNumberAction != nil {
+ headers["x-ms-sequence-number-action"] = string(*input.SequenceNumberAction)
+ }
+ if input.BlobSequenceNumber != nil {
+ headers["x-ms-blob-sequence-number"] = *input.BlobSequenceNumber
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetPropertiesSender sends the SetProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetPropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetPropertiesResponder handles the response to the SetProperties request. The method always
+// closes the http.Response Body.
+func (client Client) SetPropertiesResponder(resp *http.Response) (result SetPropertiesResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.BlobSequenceNumber = resp.Header.Get("x-ms-blob-sequence-number")
+ result.Etag = resp.Header.Get("Etag")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/put_append_blob.go b/storage/2018-11-09/blob/blobs/put_append_blob.go
new file mode 100644
index 0000000..ef2c502
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/put_append_blob.go
@@ -0,0 +1,134 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type PutAppendBlobInput struct {
+ CacheControl *string
+ ContentDisposition *string
+ ContentEncoding *string
+ ContentLanguage *string
+ ContentMD5 *string
+ ContentType *string
+ LeaseID *string
+ MetaData map[string]string
+}
+
+// PutAppendBlob is a wrapper around the Put API call (with a stricter input object)
+// which creates a new append blob, or updates the content of an existing blob.
+func (client Client) PutAppendBlob(ctx context.Context, accountName, containerName, blobName string, input PutAppendBlobInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutAppendBlob", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutAppendBlob", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutAppendBlob", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutAppendBlob", "`blobName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("blobs.Client", "PutAppendBlob", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.PutAppendBlobPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutAppendBlob", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutAppendBlobSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutAppendBlob", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutAppendBlobResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutAppendBlob", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutAppendBlobPreparer prepares the PutAppendBlob request.
+func (client Client) PutAppendBlobPreparer(ctx context.Context, accountName, containerName, blobName string, input PutAppendBlobInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-blob-type": string(AppendBlob),
+ "x-ms-version": APIVersion,
+
+ // For a page blob or an append blob, the value of this header must be set to zero,
+ // as Put Blob is used only to initialize the blob
+ "Content-Length": 0,
+ }
+
+ if input.CacheControl != nil {
+ headers["x-ms-blob-cache-control"] = *input.CacheControl
+ }
+ if input.ContentDisposition != nil {
+ headers["x-ms-blob-content-disposition"] = *input.ContentDisposition
+ }
+ if input.ContentEncoding != nil {
+ headers["x-ms-blob-content-encoding"] = *input.ContentEncoding
+ }
+ if input.ContentLanguage != nil {
+ headers["x-ms-blob-content-language"] = *input.ContentLanguage
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.ContentType != nil {
+ headers["x-ms-blob-content-type"] = *input.ContentType
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutAppendBlobSender sends the PutAppendBlob request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutAppendBlobSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutAppendBlobResponder handles the response to the PutAppendBlob request. The method always
+// closes the http.Response Body.
+func (client Client) PutAppendBlobResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/put_block.go b/storage/2018-11-09/blob/blobs/put_block.go
new file mode 100644
index 0000000..5256013
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/put_block.go
@@ -0,0 +1,125 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type PutBlockInput struct {
+ BlockID string
+ Content []byte
+ ContentMD5 *string
+ LeaseID *string
+}
+
+type PutBlockResult struct {
+ autorest.Response
+
+ ContentMD5 string
+}
+
+// PutBlock creates a new block to be committed as part of a blob.
+func (client Client) PutBlock(ctx context.Context, accountName, containerName, blobName string, input PutBlockInput) (result PutBlockResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlock", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlock", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutBlock", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlock", "`blobName` cannot be an empty string.")
+ }
+ if input.BlockID == "" {
+ return result, validation.NewError("blobs.Client", "PutBlock", "`input.BlockID` cannot be an empty string.")
+ }
+ if len(input.Content) == 0 {
+ return result, validation.NewError("blobs.Client", "PutBlock", "`input.Content` cannot be empty.")
+ }
+
+ req, err := client.PutBlockPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlock", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutBlockSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlock", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutBlockResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlock", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutBlockPreparer prepares the PutBlock request.
+func (client Client) PutBlockPreparer(ctx context.Context, accountName, containerName, blobName string, input PutBlockInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "block"),
+ "blockid": autorest.Encode("query", input.BlockID),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithBytes(&input.Content))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutBlockSender sends the PutBlock request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutBlockSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutBlockResponder handles the response to the PutBlock request. The method always
+// closes the http.Response Body.
+func (client Client) PutBlockResponder(resp *http.Response) (result PutBlockResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.ContentMD5 = resp.Header.Get("Content-MD5")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/put_block_blob.go b/storage/2018-11-09/blob/blobs/put_block_blob.go
new file mode 100644
index 0000000..fa29dd3
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/put_block_blob.go
@@ -0,0 +1,135 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type PutBlockBlobInput struct {
+ CacheControl *string
+ Content []byte
+ ContentDisposition *string
+ ContentEncoding *string
+ ContentLanguage *string
+ ContentMD5 *string
+ ContentType *string
+ LeaseID *string
+ MetaData map[string]string
+}
+
+// PutBlockBlob is a wrapper around the Put API call (with a stricter input object)
+// which creates a new block append blob, or updates the content of an existing block blob.
+func (client Client) PutBlockBlob(ctx context.Context, accountName, containerName, blobName string, input PutBlockBlobInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockBlob", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockBlob", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutBlockBlob", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockBlob", "`blobName` cannot be an empty string.")
+ }
+ if len(input.Content) == 0 {
+ return result, validation.NewError("blobs.Client", "PutBlockBlob", "`input.Content` cannot be empty.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("blobs.Client", "PutBlockBlob", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.PutBlockBlobPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockBlob", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutBlockBlobSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockBlob", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutBlockBlobResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockBlob", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutBlockBlobPreparer prepares the PutBlockBlob request.
+func (client Client) PutBlockBlobPreparer(ctx context.Context, accountName, containerName, blobName string, input PutBlockBlobInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-blob-type": string(BlockBlob),
+ "x-ms-version": APIVersion,
+ }
+
+ if input.CacheControl != nil {
+ headers["x-ms-blob-cache-control"] = *input.CacheControl
+ }
+ if input.ContentDisposition != nil {
+ headers["x-ms-blob-content-disposition"] = *input.ContentDisposition
+ }
+ if input.ContentEncoding != nil {
+ headers["x-ms-blob-content-encoding"] = *input.ContentEncoding
+ }
+ if input.ContentLanguage != nil {
+ headers["x-ms-blob-content-language"] = *input.ContentLanguage
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.ContentType != nil {
+ headers["x-ms-blob-content-type"] = *input.ContentType
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithBytes(&input.Content))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutBlockBlobSender sends the PutBlockBlob request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutBlockBlobSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutBlockBlobResponder handles the response to the PutBlockBlob request. The method always
+// closes the http.Response Body.
+func (client Client) PutBlockBlobResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/put_block_blob_file.go b/storage/2018-11-09/blob/blobs/put_block_blob_file.go
new file mode 100644
index 0000000..7232e5e
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/put_block_blob_file.go
@@ -0,0 +1,34 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "os"
+)
+
+// PutBlockBlobFromFile is a helper method which takes a file, and automatically chunks it up, rather than having to do this yourself
+func (client Client) PutBlockBlobFromFile(ctx context.Context, accountName, containerName, blobName string, file *os.File, input PutBlockBlobInput) error {
+ fileInfo, err := file.Stat()
+ if err != nil {
+ return fmt.Errorf("Error loading file info: %s", err)
+ }
+
+ fileSize := fileInfo.Size()
+ bytes := make([]byte, fileSize)
+
+ _, err = file.ReadAt(bytes, 0)
+ if err != nil {
+ if err != io.EOF {
+ return fmt.Errorf("Error reading bytes: %s", err)
+ }
+ }
+
+ input.Content = bytes
+
+ if _, err = client.PutBlockBlob(ctx, accountName, containerName, blobName, input); err != nil {
+ return fmt.Errorf("Error putting bytes: %s", err)
+ }
+
+ return nil
+}
diff --git a/storage/2018-11-09/blob/blobs/put_block_list.go b/storage/2018-11-09/blob/blobs/put_block_list.go
new file mode 100644
index 0000000..f805247
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/put_block_list.go
@@ -0,0 +1,157 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type BlockList struct {
+ CommittedBlockIDs []BlockID `xml:"Committed,omitempty"`
+ UncommittedBlockIDs []BlockID `xml:"Uncommitted,omitempty"`
+ LatestBlockIDs []BlockID `xml:"Latest,omitempty"`
+}
+
+type BlockID struct {
+ Value string `xml:",chardata"`
+}
+
+type PutBlockListInput struct {
+ BlockList BlockList
+ CacheControl *string
+ ContentDisposition *string
+ ContentEncoding *string
+ ContentLanguage *string
+ ContentMD5 *string
+ ContentType *string
+ MetaData map[string]string
+ LeaseID *string
+}
+
+type PutBlockListResult struct {
+ autorest.Response
+
+ ContentMD5 string
+ ETag string
+ LastModified string
+}
+
+// PutBlockList writes a blob by specifying the list of block IDs that make up the blob.
+// In order to be written as part of a blob, a block must have been successfully written
+// to the server in a prior Put Block operation.
+func (client Client) PutBlockList(ctx context.Context, accountName, containerName, blobName string, input PutBlockListInput) (result PutBlockListResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockList", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockList", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutBlockList", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockList", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.PutBlockListPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockList", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutBlockListSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockList", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutBlockListResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockList", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutBlockListPreparer prepares the PutBlockList request.
+func (client Client) PutBlockListPreparer(ctx context.Context, accountName, containerName, blobName string, input PutBlockListInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "blocklist"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.CacheControl != nil {
+ headers["x-ms-blob-cache-control"] = *input.CacheControl
+ }
+ if input.ContentDisposition != nil {
+ headers["x-ms-blob-content-disposition"] = *input.ContentDisposition
+ }
+ if input.ContentEncoding != nil {
+ headers["x-ms-blob-content-encoding"] = *input.ContentEncoding
+ }
+ if input.ContentLanguage != nil {
+ headers["x-ms-blob-content-language"] = *input.ContentLanguage
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.ContentType != nil {
+ headers["x-ms-blob-content-type"] = *input.ContentType
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithXML(input.BlockList))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutBlockListSender sends the PutBlockList request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutBlockListSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutBlockListResponder handles the response to the PutBlockList request. The method always
+// closes the http.Response Body.
+func (client Client) PutBlockListResponder(resp *http.Response) (result PutBlockListResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.ContentMD5 = resp.Header.Get("Content-MD5")
+ result.ETag = resp.Header.Get("ETag")
+ result.LastModified = resp.Header.Get("Last-Modified")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/put_block_url.go b/storage/2018-11-09/blob/blobs/put_block_url.go
new file mode 100644
index 0000000..95ad974
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/put_block_url.go
@@ -0,0 +1,129 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type PutBlockFromURLInput struct {
+ BlockID string
+ CopySource string
+
+ ContentMD5 *string
+ LeaseID *string
+ Range *string
+}
+
+type PutBlockFromURLResult struct {
+ autorest.Response
+ ContentMD5 string
+}
+
+// PutBlockFromURL creates a new block to be committed as part of a blob where the contents are read from a URL
+func (client Client) PutBlockFromURL(ctx context.Context, accountName, containerName, blobName string, input PutBlockFromURLInput) (result PutBlockFromURLResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockFromURL", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockFromURL", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutBlockFromURL", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockFromURL", "`blobName` cannot be an empty string.")
+ }
+ if input.BlockID == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockFromURL", "`input.BlockID` cannot be an empty string.")
+ }
+ if input.CopySource == "" {
+ return result, validation.NewError("blobs.Client", "PutBlockFromURL", "`input.CopySource` cannot be an empty string.")
+ }
+
+ req, err := client.PutBlockFromURLPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockFromURL", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutBlockFromURLSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockFromURL", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutBlockFromURLResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutBlockFromURL", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutBlockFromURLPreparer prepares the PutBlockFromURL request.
+func (client Client) PutBlockFromURLPreparer(ctx context.Context, accountName, containerName, blobName string, input PutBlockFromURLInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "block"),
+ "blockid": autorest.Encode("query", input.BlockID),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-copy-source": input.CopySource,
+ }
+
+ if input.ContentMD5 != nil {
+ headers["x-ms-source-content-md5"] = *input.ContentMD5
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+ if input.Range != nil {
+ headers["x-ms-source-range"] = *input.Range
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutBlockFromURLSender sends the PutBlockFromURL request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutBlockFromURLSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutBlockFromURLResponder handles the response to the PutBlockFromURL request. The method always
+// closes the http.Response Body.
+func (client Client) PutBlockFromURLResponder(resp *http.Response) (result PutBlockFromURLResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.ContentMD5 = resp.Header.Get("Content-MD5")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/put_page_blob.go b/storage/2018-11-09/blob/blobs/put_page_blob.go
new file mode 100644
index 0000000..ad3c878
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/put_page_blob.go
@@ -0,0 +1,148 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type PutPageBlobInput struct {
+ CacheControl *string
+ ContentDisposition *string
+ ContentEncoding *string
+ ContentLanguage *string
+ ContentMD5 *string
+ ContentType *string
+ LeaseID *string
+ MetaData map[string]string
+
+ BlobContentLengthBytes int64
+ BlobSequenceNumber *int64
+ AccessTier *AccessTier
+}
+
+// PutPageBlob is a wrapper around the Put API call (with a stricter input object)
+// which creates a new block blob, or updates the content of an existing page blob.
+func (client Client) PutPageBlob(ctx context.Context, accountName, containerName, blobName string, input PutPageBlobInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageBlob", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageBlob", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutPageBlob", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageBlob", "`blobName` cannot be an empty string.")
+ }
+ if input.BlobContentLengthBytes == 0 || input.BlobContentLengthBytes%512 != 0 {
+ return result, validation.NewError("blobs.Client", "PutPageBlob", "`blobName` must be aligned to a 512-byte boundary.")
+ }
+
+ req, err := client.PutPageBlobPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageBlob", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutPageBlobSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageBlob", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutPageBlobResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageBlob", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutPageBlobPreparer prepares the PutPageBlob request.
+func (client Client) PutPageBlobPreparer(ctx context.Context, accountName, containerName, blobName string, input PutPageBlobInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-blob-type": string(PageBlob),
+ "x-ms-version": APIVersion,
+
+ // For a page blob or an page blob, the value of this header must be set to zero,
+ // as Put Blob is used only to initialize the blob
+ "Content-Length": 0,
+
+ // This header specifies the maximum size for the page blob, up to 8 TB.
+ // The page blob size must be aligned to a 512-byte boundary.
+ "x-ms-blob-content-length": input.BlobContentLengthBytes,
+ }
+
+ if input.AccessTier != nil {
+ headers["x-ms-access-tier"] = string(*input.AccessTier)
+ }
+ if input.BlobSequenceNumber != nil {
+ headers["x-ms-blob-sequence-number"] = *input.BlobSequenceNumber
+ }
+
+ if input.CacheControl != nil {
+ headers["x-ms-blob-cache-control"] = *input.CacheControl
+ }
+ if input.ContentDisposition != nil {
+ headers["x-ms-blob-content-disposition"] = *input.ContentDisposition
+ }
+ if input.ContentEncoding != nil {
+ headers["x-ms-blob-content-encoding"] = *input.ContentEncoding
+ }
+ if input.ContentLanguage != nil {
+ headers["x-ms-blob-content-language"] = *input.ContentLanguage
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-blob-content-md5"] = *input.ContentMD5
+ }
+ if input.ContentType != nil {
+ headers["x-ms-blob-content-type"] = *input.ContentType
+ }
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutPageBlobSender sends the PutPageBlob request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutPageBlobSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutPageBlobResponder handles the response to the PutPageBlob request. The method always
+// closes the http.Response Body.
+func (client Client) PutPageBlobResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/put_page_clear.go b/storage/2018-11-09/blob/blobs/put_page_clear.go
new file mode 100644
index 0000000..59feaa5
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/put_page_clear.go
@@ -0,0 +1,113 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type PutPageClearInput struct {
+ StartByte int64
+ EndByte int64
+
+ LeaseID *string
+}
+
+// PutPageClear clears a range of pages within a page blob.
+func (client Client) PutPageClear(ctx context.Context, accountName, containerName, blobName string, input PutPageClearInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageClear", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageClear", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutPageClear", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageClear", "`blobName` cannot be an empty string.")
+ }
+ if input.StartByte < 0 {
+ return result, validation.NewError("blobs.Client", "PutPageClear", "`input.StartByte` must be greater than or equal to 0.")
+ }
+ if input.EndByte <= 0 {
+ return result, validation.NewError("blobs.Client", "PutPageClear", "`input.EndByte` must be greater than 0.")
+ }
+
+ req, err := client.PutPageClearPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageClear", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutPageClearSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageClear", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutPageClearResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageClear", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutPageClearPreparer prepares the PutPageClear request.
+func (client Client) PutPageClearPreparer(ctx context.Context, accountName, containerName, blobName string, input PutPageClearInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "page"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-page-write": "clear",
+ "x-ms-range": fmt.Sprintf("bytes=%d-%d", input.StartByte, input.EndByte),
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutPageClearSender sends the PutPageClear request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutPageClearSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutPageClearResponder handles the response to the PutPageClear request. The method always
+// closes the http.Response Body.
+func (client Client) PutPageClearResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/put_page_update.go b/storage/2018-11-09/blob/blobs/put_page_update.go
new file mode 100644
index 0000000..a47e8ca
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/put_page_update.go
@@ -0,0 +1,163 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type PutPageUpdateInput struct {
+ StartByte int64
+ EndByte int64
+ Content []byte
+
+ IfSequenceNumberEQ *string
+ IfSequenceNumberLE *string
+ IfSequenceNumberLT *string
+ IfModifiedSince *string
+ IfUnmodifiedSince *string
+ IfMatch *string
+ IfNoneMatch *string
+ LeaseID *string
+}
+
+type PutPageUpdateResult struct {
+ autorest.Response
+
+ BlobSequenceNumber string
+ ContentMD5 string
+ LastModified string
+}
+
+// PutPageUpdate writes a range of pages to a page blob.
+func (client Client) PutPageUpdate(ctx context.Context, accountName, containerName, blobName string, input PutPageUpdateInput) (result PutPageUpdateResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", "`blobName` cannot be an empty string.")
+ }
+ if input.StartByte < 0 {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", "`input.StartByte` must be greater than or equal to 0.")
+ }
+ if input.EndByte <= 0 {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", "`input.EndByte` must be greater than 0.")
+ }
+
+ expectedSize := (input.EndByte - input.StartByte) + 1
+ actualSize := int64(len(input.Content))
+ if expectedSize != actualSize {
+ return result, validation.NewError("blobs.Client", "PutPageUpdate", fmt.Sprintf("Content Size was defined as %d but got %d.", expectedSize, actualSize))
+ }
+
+ req, err := client.PutPageUpdatePreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageUpdate", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutPageUpdateSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageUpdate", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutPageUpdateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "PutPageUpdate", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutPageUpdatePreparer prepares the PutPageUpdate request.
+func (client Client) PutPageUpdatePreparer(ctx context.Context, accountName, containerName, blobName string, input PutPageUpdateInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "page"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-page-write": "update",
+ "x-ms-range": fmt.Sprintf("bytes=%d-%d", input.StartByte, input.EndByte),
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+ if input.IfSequenceNumberEQ != nil {
+ headers["x-ms-if-sequence-number-eq"] = *input.IfSequenceNumberEQ
+ }
+ if input.IfSequenceNumberLE != nil {
+ headers["x-ms-if-sequence-number-le"] = *input.IfSequenceNumberLE
+ }
+ if input.IfSequenceNumberLT != nil {
+ headers["x-ms-if-sequence-number-lt"] = *input.IfSequenceNumberLT
+ }
+ if input.IfModifiedSince != nil {
+ headers["If-Modified-Since"] = *input.IfModifiedSince
+ }
+ if input.IfUnmodifiedSince != nil {
+ headers["If-Unmodified-Since"] = *input.IfUnmodifiedSince
+ }
+ if input.IfMatch != nil {
+ headers["If-Match"] = *input.IfMatch
+ }
+ if input.IfNoneMatch != nil {
+ headers["If-None-Match"] = *input.IfNoneMatch
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithBytes(&input.Content))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutPageUpdateSender sends the PutPageUpdate request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutPageUpdateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutPageUpdateResponder handles the response to the PutPageUpdate request. The method always
+// closes the http.Response Body.
+func (client Client) PutPageUpdateResponder(resp *http.Response) (result PutPageUpdateResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.BlobSequenceNumber = resp.Header.Get("x-ms-blob-sequence-number")
+ result.ContentMD5 = resp.Header.Get("Content-MD5")
+ result.LastModified = resp.Header.Get("Last-Modified")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/resource_id.go b/storage/2018-11-09/blob/blobs/resource_id.go
new file mode 100644
index 0000000..0f6dddf
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/resource_id.go
@@ -0,0 +1,56 @@
+package blobs
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Blob
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, containerName, blobName string) string {
+ domain := endpoints.GetBlobEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s/%s", domain, containerName, blobName)
+}
+
+type ResourceID struct {
+ AccountName string
+ ContainerName string
+ BlobName string
+}
+
+// ParseResourceID parses the Resource ID and returns an object which can be used
+// to interact with the Blob Resource
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://foo.blob.core.windows.net/Bar/example.vhd
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ path := strings.TrimPrefix(uri.Path, "/")
+ segments := strings.Split(path, "/")
+ if len(segments) == 0 {
+ return nil, fmt.Errorf("Expected the path to contain segments but got none")
+ }
+
+ containerName := segments[0]
+ blobName := strings.TrimPrefix(path, containerName)
+ blobName = strings.TrimPrefix(blobName, "/")
+ return &ResourceID{
+ AccountName: *accountName,
+ ContainerName: containerName,
+ BlobName: blobName,
+ }, nil
+}
diff --git a/storage/2018-11-09/blob/blobs/resource_id_test.go b/storage/2018-11-09/blob/blobs/resource_id_test.go
new file mode 100644
index 0000000..bb6cad1
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/resource_id_test.go
@@ -0,0 +1,123 @@
+package blobs
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.blob.core.chinacloudapi.cn/container1/blob1.vhd",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.blob.core.cloudapi.de/container1/blob1.vhd",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.blob.core.windows.net/container1/blob1.vhd",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.blob.core.usgovcloudapi.net/container1/blob1.vhd",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "container1", "blob1.vhd")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.blob.core.chinacloudapi.cn/container1/blob1.vhd",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.blob.core.cloudapi.de/container1/blob1.vhd",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.blob.core.windows.net/container1/blob1.vhd",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.blob.core.usgovcloudapi.net/container1/blob1.vhd",
+ },
+ }
+ t.Logf("[DEBUG] Top Level Files")
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.ContainerName != "container1" {
+ t.Fatalf("Expected Container Name to be `container1` but got %q", actual.ContainerName)
+ }
+ if actual.BlobName != "blob1.vhd" {
+ t.Fatalf("Expected Blob Name to be `blob1.vhd` but got %q", actual.BlobName)
+ }
+ }
+
+ testData = []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.blob.core.chinacloudapi.cn/container1/example/blob1.vhd",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.blob.core.cloudapi.de/container1/example/blob1.vhd",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.blob.core.windows.net/container1/example/blob1.vhd",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.blob.core.usgovcloudapi.net/container1/example/blob1.vhd",
+ },
+ }
+ t.Logf("[DEBUG] Nested Files")
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.ContainerName != "container1" {
+ t.Fatalf("Expected Container Name to be `container1` but got %q", actual.ContainerName)
+ }
+ if actual.BlobName != "example/blob1.vhd" {
+ t.Fatalf("Expected Blob Name to be `example/blob1.vhd` but got %q", actual.BlobName)
+ }
+ }
+}
diff --git a/storage/2018-11-09/blob/blobs/set_tier.go b/storage/2018-11-09/blob/blobs/set_tier.go
new file mode 100644
index 0000000..dd0f0b8
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/set_tier.go
@@ -0,0 +1,93 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// SetTier sets the tier on a blob.
+func (client Client) SetTier(ctx context.Context, accountName, containerName, blobName string, tier AccessTier) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "SetTier", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "SetTier", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "SetTier", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "SetTier", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.SetTierPreparer(ctx, accountName, containerName, blobName, tier)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetTier", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetTierSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetTier", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetTierResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "SetTier", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetTierPreparer prepares the SetTier request.
+func (client Client) SetTierPreparer(ctx context.Context, accountName, containerName, blobName string, tier AccessTier) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "tier"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-access-tier": string(tier),
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetTierSender sends the SetTier request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetTierSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetTierResponder handles the response to the SetTier request. The method always
+// closes the http.Response Body.
+func (client Client) SetTierResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/snapshot.go b/storage/2018-11-09/blob/blobs/snapshot.go
new file mode 100644
index 0000000..180070b
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/snapshot.go
@@ -0,0 +1,163 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type SnapshotInput struct {
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+
+ // MetaData is a user-defined name-value pair associated with the blob.
+ // If no name-value pairs are specified, the operation will copy the base blob metadata to the snapshot.
+ // If one or more name-value pairs are specified, the snapshot is created with the specified metadata,
+ // and metadata is not copied from the base blob.
+ MetaData map[string]string
+
+ // A DateTime value which will only snapshot the blob if it has been modified since the specified date/time
+ // If the base blob has not been modified, the Blob service returns status code 412 (Precondition Failed).
+ IfModifiedSince *string
+
+ // A DateTime value which will only snapshot the blob if it has not been modified since the specified date/time
+ // If the base blob has been modified, the Blob service returns status code 412 (Precondition Failed).
+ IfUnmodifiedSince *string
+
+ // An ETag value to snapshot the blob only if its ETag value matches the value specified.
+ // If the values do not match, the Blob service returns status code 412 (Precondition Failed).
+ IfMatch *string
+
+ // An ETag value for this conditional header to snapshot the blob only if its ETag value
+ // does not match the value specified.
+ // If the values are identical, the Blob service returns status code 412 (Precondition Failed).
+ IfNoneMatch *string
+}
+
+type SnapshotResult struct {
+ autorest.Response
+
+ // The ETag of the snapshot
+ ETag string
+
+ // A DateTime value that uniquely identifies the snapshot.
+ // The value of this header indicates the snapshot version,
+ // and may be used in subsequent requests to access the snapshot.
+ SnapshotDateTime string
+}
+
+// Snapshot captures a Snapshot of a given Blob
+func (client Client) Snapshot(ctx context.Context, accountName, containerName, blobName string, input SnapshotInput) (result SnapshotResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "Snapshot", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "Snapshot", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "Snapshot", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "Snapshot", "`blobName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("blobs.Client", "Snapshot", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.SnapshotPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Snapshot", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SnapshotSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Snapshot", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SnapshotResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Snapshot", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SnapshotPreparer prepares the Snapshot request.
+func (client Client) SnapshotPreparer(ctx context.Context, accountName, containerName, blobName string, input SnapshotInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "snapshot"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ if input.IfModifiedSince != nil {
+ headers["If-Modified-Since"] = *input.IfModifiedSince
+ }
+ if input.IfUnmodifiedSince != nil {
+ headers["If-Unmodified-Since"] = *input.IfUnmodifiedSince
+ }
+ if input.IfMatch != nil {
+ headers["If-Match"] = *input.IfMatch
+ }
+ if input.IfNoneMatch != nil {
+ headers["If-None-Match"] = *input.IfNoneMatch
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SnapshotSender sends the Snapshot request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SnapshotSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SnapshotResponder handles the response to the Snapshot request. The method always
+// closes the http.Response Body.
+func (client Client) SnapshotResponder(resp *http.Response) (result SnapshotResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.ETag = resp.Header.Get("ETag")
+ result.SnapshotDateTime = resp.Header.Get("x-ms-snapshot")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/snapshot_get_properties.go b/storage/2018-11-09/blob/blobs/snapshot_get_properties.go
new file mode 100644
index 0000000..fe1be63
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/snapshot_get_properties.go
@@ -0,0 +1,90 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetSnapshotPropertiesInput struct {
+ // The ID of the Lease
+ // This must be specified if a Lease is present on the Blob, else a 403 is returned
+ LeaseID *string
+
+ // The ID of the Snapshot which should be retrieved
+ SnapshotID string
+}
+
+// GetSnapshotProperties returns all user-defined metadata, standard HTTP properties, and system properties for
+// the specified snapshot of a blob
+func (client Client) GetSnapshotProperties(ctx context.Context, accountName, containerName, blobName string, input GetSnapshotPropertiesInput) (result GetPropertiesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "GetSnapshotProperties", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "GetSnapshotProperties", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "GetSnapshotProperties", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "GetSnapshotProperties", "`blobName` cannot be an empty string.")
+ }
+ if input.SnapshotID == "" {
+ return result, validation.NewError("blobs.Client", "GetSnapshotProperties", "`input.SnapshotID` cannot be an empty string.")
+ }
+
+ req, err := client.GetSnapshotPropertiesPreparer(ctx, accountName, containerName, blobName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetSnapshotProperties", nil, "Failure preparing request")
+ return
+ }
+
+ // we re-use the GetProperties methods since this is otherwise the same
+ resp, err := client.GetPropertiesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetSnapshotProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "GetSnapshotProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetSnapshotPreparer prepares the GetSnapshot request.
+func (client Client) GetSnapshotPropertiesPreparer(ctx context.Context, accountName, containerName, blobName string, input GetSnapshotPropertiesInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "snapshot": autorest.Encode("query", input.SnapshotID),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if input.LeaseID != nil {
+ headers["x-ms-lease-id"] = *input.LeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsHead(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
diff --git a/storage/2018-11-09/blob/blobs/snapshot_test.go b/storage/2018-11-09/blob/blobs/snapshot_test.go
new file mode 100644
index 0000000..d8c6ae5
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/snapshot_test.go
@@ -0,0 +1,159 @@
+package blobs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "testing"
+ "time"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestSnapshotLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+ fileName := "example.txt"
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ containersClient := containers.NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithStorageResourceManagerAuth(containersClient.Client)
+
+ _, err = containersClient.Create(ctx, accountName, containerName, containers.CreateInput{})
+ if err != nil {
+ t.Fatalf("Error creating: %s", err)
+ }
+ defer containersClient.Delete(ctx, accountName, containerName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ blobClient := NewWithEnvironment(client.Environment)
+ blobClient.Client = client.PrepareWithAuthorizer(blobClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Copying file to Blob Storage..")
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+
+ refreshInterval := 5 * time.Second
+ if err := blobClient.CopyAndWait(ctx, accountName, containerName, fileName, copyInput, refreshInterval); err != nil {
+ t.Fatalf("Error copying: %s", err)
+ }
+
+ t.Logf("[DEBUG] First Snapshot..")
+ firstSnapshot, err := blobClient.Snapshot(ctx, accountName, containerName, fileName, SnapshotInput{})
+ if err != nil {
+ t.Fatalf("Error taking first snapshot: %s", err)
+ }
+ t.Logf("[DEBUG] First Snapshot ID: %q", firstSnapshot.SnapshotDateTime)
+
+ t.Log("[DEBUG] Waiting 2 seconds..")
+ time.Sleep(2 * time.Second)
+
+ t.Logf("[DEBUG] Second Snapshot..")
+ secondSnapshot, err := blobClient.Snapshot(ctx, accountName, containerName, fileName, SnapshotInput{
+ MetaData: map[string]string{
+ "hello": "world",
+ },
+ })
+ if err != nil {
+ t.Fatalf("Error taking Second snapshot: %s", err)
+ }
+ t.Logf("[DEBUG] Second Snapshot ID: %q", secondSnapshot.SnapshotDateTime)
+
+ t.Logf("[DEBUG] Leasing the Blob..")
+ leaseDetails, err := blobClient.AcquireLease(ctx, accountName, containerName, fileName, AcquireLeaseInput{
+ // infinite
+ LeaseDuration: -1,
+ })
+ if err != nil {
+ t.Fatalf("Error leasing Blob: %s", err)
+ }
+ t.Logf("[DEBUG] Lease ID: %q", leaseDetails.LeaseID)
+
+ t.Logf("[DEBUG] Third Snapshot..")
+ thirdSnapshot, err := blobClient.Snapshot(ctx, accountName, containerName, fileName, SnapshotInput{
+ LeaseID: &leaseDetails.LeaseID,
+ })
+ if err != nil {
+ t.Fatalf("Error taking Third snapshot: %s", err)
+ }
+ t.Logf("[DEBUG] Third Snapshot ID: %q", thirdSnapshot.SnapshotDateTime)
+
+ t.Logf("[DEBUG] Releasing Lease..")
+ if _, err := blobClient.ReleaseLease(ctx, accountName, containerName, fileName, leaseDetails.LeaseID); err != nil {
+ t.Fatalf("Error releasing Lease: %s", err)
+ }
+
+ // get the properties from the blob, which should include the LastModifiedDate
+ t.Logf("[DEBUG] Retrieving Properties for Blob")
+ props, err := blobClient.GetProperties(ctx, accountName, containerName, fileName, GetPropertiesInput{})
+ if err != nil {
+ t.Fatalf("Error getting properties: %s", err)
+ }
+
+ // confirm that the If-Modified-None returns an error
+ t.Logf("[DEBUG] Third Snapshot..")
+ fourthSnapshot, err := blobClient.Snapshot(ctx, accountName, containerName, fileName, SnapshotInput{
+ LeaseID: &leaseDetails.LeaseID,
+ IfModifiedSince: &props.LastModified,
+ })
+ if err == nil {
+ t.Fatalf("Expected an error but didn't get one")
+ }
+ if fourthSnapshot.Response.StatusCode != http.StatusPreconditionFailed {
+ t.Fatalf("Expected the status code to be Precondition Failed but got: %d", fourthSnapshot.Response.StatusCode)
+ }
+
+ t.Logf("[DEBUG] Retrieving the Second Snapshot Properties..")
+ getSecondSnapshotInput := GetSnapshotPropertiesInput{
+ SnapshotID: secondSnapshot.SnapshotDateTime,
+ }
+ if _, err := blobClient.GetSnapshotProperties(ctx, accountName, containerName, fileName, getSecondSnapshotInput); err != nil {
+ t.Fatalf("Error retrieving properties for the second snapshot: %s", err)
+ }
+
+ t.Logf("[DEBUG] Deleting the Second Snapshot..")
+ deleteSnapshotInput := DeleteSnapshotInput{
+ SnapshotDateTime: secondSnapshot.SnapshotDateTime,
+ }
+ if _, err := blobClient.DeleteSnapshot(ctx, accountName, containerName, fileName, deleteSnapshotInput); err != nil {
+ t.Fatalf("Error deleting snapshot: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-Retrieving the Second Snapshot Properties..")
+ secondSnapshotProps, err := blobClient.GetSnapshotProperties(ctx, accountName, containerName, fileName, getSecondSnapshotInput)
+ if err == nil {
+ t.Fatalf("Expected an error retrieving the snapshot but got none")
+ }
+ if secondSnapshotProps.Response.StatusCode != http.StatusNotFound {
+ t.Fatalf("Expected the status code to be %d but got %q", http.StatusNoContent, secondSnapshotProps.Response.StatusCode)
+ }
+
+ t.Logf("[DEBUG] Deleting all the snapshots..")
+ if _, err := blobClient.DeleteSnapshots(ctx, accountName, containerName, fileName, DeleteSnapshotsInput{}); err != nil {
+ t.Fatalf("Error deleting snapshots: %s", err)
+ }
+
+ t.Logf("[DEBUG] Deleting the Blob..")
+ deleteInput := DeleteInput{
+ DeleteSnapshots: false,
+ }
+ if _, err := blobClient.Delete(ctx, accountName, containerName, fileName, deleteInput); err != nil {
+ t.Fatalf("Error deleting Blob: %s", err)
+ }
+}
diff --git a/storage/2018-11-09/blob/blobs/undelete.go b/storage/2018-11-09/blob/blobs/undelete.go
new file mode 100644
index 0000000..9be2f81
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/undelete.go
@@ -0,0 +1,92 @@
+package blobs
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Undelete restores the contents and metadata of soft deleted blob and any associated soft deleted snapshots.
+func (client Client) Undelete(ctx context.Context, accountName, containerName, blobName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("blobs.Client", "Undelete", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("blobs.Client", "Undelete", "`containerName` cannot be an empty string.")
+ }
+ if strings.ToLower(containerName) != containerName {
+ return result, validation.NewError("blobs.Client", "Undelete", "`containerName` must be a lower-cased string.")
+ }
+ if blobName == "" {
+ return result, validation.NewError("blobs.Client", "Undelete", "`blobName` cannot be an empty string.")
+ }
+
+ req, err := client.UndeletePreparer(ctx, accountName, containerName, blobName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Undelete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.UndeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Undelete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.UndeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "blobs.Client", "Undelete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// UndeletePreparer prepares the Undelete request.
+func (client Client) UndeletePreparer(ctx context.Context, accountName, containerName, blobName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ "blobName": autorest.Encode("path", blobName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "undelete"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}/{blobName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// UndeleteSender sends the Undelete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) UndeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// UndeleteResponder handles the response to the Undelete request. The method always
+// closes the http.Response Body.
+func (client Client) UndeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-11-09/blob/blobs/version.go b/storage/2018-11-09/blob/blobs/version.go
new file mode 100644
index 0000000..ad61a57
--- /dev/null
+++ b/storage/2018-11-09/blob/blobs/version.go
@@ -0,0 +1,14 @@
+package blobs
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2018-11-09"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2018-11-09/blob/containers/README.md b/storage/2018-11-09/blob/containers/README.md
new file mode 100644
index 0000000..37d2878
--- /dev/null
+++ b/storage/2018-11-09/blob/containers/README.md
@@ -0,0 +1,45 @@
+## Blob Storage Container SDK for API version 2018-11-09
+
+This package allows you to interact with the Containers Blob Storage API
+
+### Supported Authorizers
+
+* Azure Active Directory (for the Resource Endpoint `https://storage.azure.com`)
+* SharedKeyLite (Blob, File & Queue)
+
+Note: when using the `ListBlobs` operation, only `SharedKeyLite` authentication is supported.
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ containerName := "mycontainer"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ containersClient := containers.New()
+ containersClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ createInput := containers.CreateInput{
+ AccessLevel: containers.Private,
+ }
+ if _, err := containersClient.Create(ctx, accountName, containerName, createInput); err != nil {
+ return fmt.Errorf("Error creating Container: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2018-11-09/blob/containers/client.go b/storage/2018-11-09/blob/containers/client.go
new file mode 100644
index 0000000..7bf4947
--- /dev/null
+++ b/storage/2018-11-09/blob/containers/client.go
@@ -0,0 +1,34 @@
+package containers
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for Blob Storage Containers.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithBaseURI creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
+
+func (client Client) setAccessLevelIntoHeaders(headers map[string]interface{}, level AccessLevel) map[string]interface{} {
+ // If this header is not included in the request, container data is private to the account owner.
+ if level != Private {
+ headers["x-ms-blob-public-access"] = string(level)
+ }
+
+ return headers
+}
diff --git a/storage/2018-11-09/blob/containers/create.go b/storage/2018-11-09/blob/containers/create.go
new file mode 100644
index 0000000..84c2887
--- /dev/null
+++ b/storage/2018-11-09/blob/containers/create.go
@@ -0,0 +1,123 @@
+package containers
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type CreateInput struct {
+ // Specifies whether data in the container may be accessed publicly and the level of access
+ AccessLevel AccessLevel
+
+ // A name-value pair to associate with the container as metadata.
+ MetaData map[string]string
+}
+
+type CreateResponse struct {
+ autorest.Response
+ Error *ErrorResponse `xml:"Error"`
+}
+
+// Create creates a new container under the specified account.
+// If the container with the same name already exists, the operation fails.
+func (client Client) Create(ctx context.Context, accountName, containerName string, input CreateInput) (result CreateResponse, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "Create", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "Create", "`containerName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("containers.Client", "Create", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.CreatePreparer(ctx, accountName, containerName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "Create", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "Create", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "Create", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreatePreparer prepares the Create request.
+func (client Client) CreatePreparer(ctx context.Context, accountName string, containerName string, input CreateInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = client.setAccessLevelIntoHeaders(headers, input.AccessLevel)
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSender sends the Create request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateResponder handles the response to the Create request. The method always
+// closes the http.Response Body.
+func (client Client) CreateResponder(resp *http.Response) (result CreateResponse, err error) {
+ successfulStatusCodes := []int{
+ http.StatusCreated,
+ }
+ if autorest.ResponseHasStatusCode(resp, successfulStatusCodes...) {
+ // when successful there's no response
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(successfulStatusCodes...),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ } else {
+ // however when there's an error the error's in the response
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(successfulStatusCodes...),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+ }
+
+ return
+}
diff --git a/storage/2018-11-09/blob/containers/delete.go b/storage/2018-11-09/blob/containers/delete.go
new file mode 100644
index 0000000..3095829
--- /dev/null
+++ b/storage/2018-11-09/blob/containers/delete.go
@@ -0,0 +1,85 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete marks the specified container for deletion.
+// The container and any blobs contained within it are later deleted during garbage collection.
+func (client Client) Delete(ctx context.Context, accountName, containerName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "Delete", "`containerName` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, containerName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName string, containerName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+ return
+}
diff --git a/storage/2018-11-09/blob/containers/get_properties.go b/storage/2018-11-09/blob/containers/get_properties.go
new file mode 100644
index 0000000..1e308da
--- /dev/null
+++ b/storage/2018-11-09/blob/containers/get_properties.go
@@ -0,0 +1,124 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// GetProperties returns the properties for this Container without a Lease
+func (client Client) GetProperties(ctx context.Context, accountName, containerName string) (ContainerProperties, error) {
+ // If specified, Get Container Properties only succeeds if the container’s lease is active and matches this ID.
+ // If there is no active lease or the ID does not match, 412 (Precondition Failed) is returned.
+ return client.GetPropertiesWithLeaseID(ctx, accountName, containerName, "")
+}
+
+// GetPropertiesWithLeaseID returns the properties for this Container using the specified LeaseID
+func (client Client) GetPropertiesWithLeaseID(ctx context.Context, accountName, containerName, leaseID string) (result ContainerProperties, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "GetPropertiesWithLeaseID", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "GetPropertiesWithLeaseID", "`containerName` cannot be an empty string.")
+ }
+
+ req, err := client.GetPropertiesWithLeaseIDPreparer(ctx, accountName, containerName, leaseID)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "GetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetPropertiesWithLeaseIDSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "GetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetPropertiesWithLeaseIDResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "GetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPropertiesWithLeaseIDPreparer prepares the GetPropertiesWithLeaseID request.
+func (client Client) GetPropertiesWithLeaseIDPreparer(ctx context.Context, accountName, containerName, leaseID string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ // If specified, Get Container Properties only succeeds if the container’s lease is active and matches this ID.
+ // If there is no active lease or the ID does not match, 412 (Precondition Failed) is returned.
+ if leaseID != "" {
+ headers["x-ms-lease-id"] = leaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetPropertiesWithLeaseIDSender sends the GetPropertiesWithLeaseID request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetPropertiesWithLeaseIDSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetPropertiesWithLeaseIDResponder handles the response to the GetPropertiesWithLeaseID request. The method always
+// closes the http.Response Body.
+func (client Client) GetPropertiesWithLeaseIDResponder(resp *http.Response) (result ContainerProperties, err error) {
+ if resp != nil {
+ result.LeaseStatus = LeaseStatus(resp.Header.Get("x-ms-lease-status"))
+ result.LeaseState = LeaseState(resp.Header.Get("x-ms-lease-state"))
+ if result.LeaseStatus == Locked {
+ duration := LeaseDuration(resp.Header.Get("x-ms-lease-duration"))
+ result.LeaseDuration = &duration
+ }
+
+ // If this header is not returned in the response, the container is private to the account owner.
+ accessLevel := resp.Header.Get("x-ms-blob-public-access")
+ if accessLevel != "" {
+ result.AccessLevel = AccessLevel(accessLevel)
+ } else {
+ result.AccessLevel = Private
+ }
+
+ // we can't necessarily use strconv.ParseBool here since this could be nil (only in some API versions)
+ result.HasImmutabilityPolicy = strings.EqualFold(resp.Header.Get("x-ms-has-immutability-policy"), "true")
+ result.HasLegalHold = strings.EqualFold(resp.Header.Get("x-ms-has-legal-hold"), "true")
+
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/containers/lease_acquire.go b/storage/2018-11-09/blob/containers/lease_acquire.go
new file mode 100644
index 0000000..061c863
--- /dev/null
+++ b/storage/2018-11-09/blob/containers/lease_acquire.go
@@ -0,0 +1,115 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type AcquireLeaseInput struct {
+ // Specifies the duration of the lease, in seconds, or negative one (-1) for a lease that never expires.
+ // A non-infinite lease can be between 15 and 60 seconds
+ LeaseDuration int
+
+ ProposedLeaseID string
+}
+
+type AcquireLeaseResponse struct {
+ autorest.Response
+
+ LeaseID string
+}
+
+// AcquireLease establishes and manages a lock on a container for delete operations.
+func (client Client) AcquireLease(ctx context.Context, accountName, containerName string, input AcquireLeaseInput) (result AcquireLeaseResponse, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "AcquireLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "AcquireLease", "`containerName` cannot be an empty string.")
+ }
+ // An infinite lease duration is -1 seconds. A non-infinite lease can be between 15 and 60 seconds
+ if input.LeaseDuration != -1 && (input.LeaseDuration <= 15 || input.LeaseDuration >= 60) {
+ return result, validation.NewError("containers.Client", "AcquireLease", "`input.LeaseDuration` must be -1 (infinite), or between 15 and 60 seconds.")
+ }
+
+ req, err := client.AcquireLeasePreparer(ctx, accountName, containerName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "AcquireLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.AcquireLeaseSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "AcquireLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.AcquireLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "AcquireLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// AcquireLeasePreparer prepares the AcquireLease request.
+func (client Client) AcquireLeasePreparer(ctx context.Context, accountName string, containerName string, input AcquireLeaseInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ "comp": autorest.Encode("path", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "acquire",
+ "x-ms-lease-duration": input.LeaseDuration,
+ }
+
+ if input.ProposedLeaseID != "" {
+ headers["x-ms-proposed-lease-id"] = input.ProposedLeaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// AcquireLeaseSender sends the AcquireLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) AcquireLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// AcquireLeaseResponder handles the response to the AcquireLease request. The method always
+// closes the http.Response Body.
+func (client Client) AcquireLeaseResponder(resp *http.Response) (result AcquireLeaseResponse, err error) {
+ if resp != nil {
+ result.LeaseID = resp.Header.Get("x-ms-lease-id")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/containers/lease_break.go b/storage/2018-11-09/blob/containers/lease_break.go
new file mode 100644
index 0000000..08acfb7
--- /dev/null
+++ b/storage/2018-11-09/blob/containers/lease_break.go
@@ -0,0 +1,129 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+ "strconv"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type BreakLeaseInput struct {
+ // For a break operation, proposed duration the lease should continue
+ // before it is broken, in seconds, between 0 and 60.
+ // This break period is only used if it is shorter than the time remaining on the lease.
+ // If longer, the time remaining on the lease is used.
+ // A new lease will not be available before the break period has expired,
+ // but the lease may be held for longer than the break period.
+ // If this header does not appear with a break operation, a fixed-duration lease breaks
+ // after the remaining lease period elapses, and an infinite lease breaks immediately.
+ BreakPeriod *int
+
+ LeaseID string
+}
+
+type BreakLeaseResponse struct {
+ autorest.Response
+
+ // Approximate time remaining in the lease period, in seconds.
+ // If the break is immediate, 0 is returned.
+ LeaseTime int
+}
+
+// BreakLease breaks a lock based on it's Lease ID
+func (client Client) BreakLease(ctx context.Context, accountName, containerName string, input BreakLeaseInput) (result BreakLeaseResponse, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "BreakLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "BreakLease", "`containerName` cannot be an empty string.")
+ }
+ if input.LeaseID == "" {
+ return result, validation.NewError("containers.Client", "BreakLease", "`input.LeaseID` cannot be an empty string.")
+ }
+
+ req, err := client.BreakLeasePreparer(ctx, accountName, containerName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "BreakLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.BreakLeaseSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "BreakLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.BreakLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "BreakLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// BreakLeasePreparer prepares the BreakLease request.
+func (client Client) BreakLeasePreparer(ctx context.Context, accountName string, containerName string, input BreakLeaseInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ "comp": autorest.Encode("path", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "break",
+ "x-ms-lease-id": input.LeaseID,
+ }
+
+ if input.BreakPeriod != nil {
+ headers["x-ms-lease-break-period"] = *input.BreakPeriod
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// BreakLeaseSender sends the BreakLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) BreakLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// BreakLeaseResponder handles the response to the BreakLease request. The method always
+// closes the http.Response Body.
+func (client Client) BreakLeaseResponder(resp *http.Response) (result BreakLeaseResponse, err error) {
+ if resp != nil {
+ leaseRaw := resp.Header.Get("x-ms-lease-time")
+ if leaseRaw != "" {
+ i, err := strconv.Atoi(leaseRaw)
+ if err == nil {
+ result.LeaseTime = i
+ }
+ }
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/containers/lease_change.go b/storage/2018-11-09/blob/containers/lease_change.go
new file mode 100644
index 0000000..dfbcb13
--- /dev/null
+++ b/storage/2018-11-09/blob/containers/lease_change.go
@@ -0,0 +1,111 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type ChangeLeaseInput struct {
+ ExistingLeaseID string
+ ProposedLeaseID string
+}
+
+type ChangeLeaseResponse struct {
+ autorest.Response
+
+ LeaseID string
+}
+
+// ChangeLease changes the lock from one Lease ID to another Lease ID
+func (client Client) ChangeLease(ctx context.Context, accountName, containerName string, input ChangeLeaseInput) (result ChangeLeaseResponse, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "ChangeLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "ChangeLease", "`containerName` cannot be an empty string.")
+ }
+ if input.ExistingLeaseID == "" {
+ return result, validation.NewError("containers.Client", "ChangeLease", "`input.ExistingLeaseID` cannot be an empty string.")
+ }
+ if input.ProposedLeaseID == "" {
+ return result, validation.NewError("containers.Client", "ChangeLease", "`input.ProposedLeaseID` cannot be an empty string.")
+ }
+
+ req, err := client.ChangeLeasePreparer(ctx, accountName, containerName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "ChangeLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ChangeLeaseSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "ChangeLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ChangeLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "ChangeLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ChangeLeasePreparer prepares the ChangeLease request.
+func (client Client) ChangeLeasePreparer(ctx context.Context, accountName string, containerName string, input ChangeLeaseInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ "comp": autorest.Encode("path", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "change",
+ "x-ms-lease-id": input.ExistingLeaseID,
+ "x-ms-proposed-lease-id": input.ProposedLeaseID,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ChangeLeaseSender sends the ChangeLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ChangeLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ChangeLeaseResponder handles the response to the ChangeLease request. The method always
+// closes the http.Response Body.
+func (client Client) ChangeLeaseResponder(resp *http.Response) (result ChangeLeaseResponse, err error) {
+ if resp != nil {
+ result.LeaseID = resp.Header.Get("x-ms-lease-id")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/containers/lease_release.go b/storage/2018-11-09/blob/containers/lease_release.go
new file mode 100644
index 0000000..fafcf98
--- /dev/null
+++ b/storage/2018-11-09/blob/containers/lease_release.go
@@ -0,0 +1,92 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// ReleaseLease releases the lock based on the Lease ID
+func (client Client) ReleaseLease(ctx context.Context, accountName, containerName, leaseID string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "ReleaseLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "ReleaseLease", "`containerName` cannot be an empty string.")
+ }
+ if leaseID == "" {
+ return result, validation.NewError("containers.Client", "ReleaseLease", "`leaseID` cannot be an empty string.")
+ }
+
+ req, err := client.ReleaseLeasePreparer(ctx, accountName, containerName, leaseID)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "ReleaseLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ReleaseLeaseSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "ReleaseLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ReleaseLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "ReleaseLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ReleaseLeasePreparer prepares the ReleaseLease request.
+func (client Client) ReleaseLeasePreparer(ctx context.Context, accountName string, containerName string, leaseID string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ "comp": autorest.Encode("path", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "release",
+ "x-ms-lease-id": leaseID,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ReleaseLeaseSender sends the ReleaseLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ReleaseLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ReleaseLeaseResponder handles the response to the ReleaseLease request. The method always
+// closes the http.Response Body.
+func (client Client) ReleaseLeaseResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/containers/lease_renew.go b/storage/2018-11-09/blob/containers/lease_renew.go
new file mode 100644
index 0000000..3fe1765
--- /dev/null
+++ b/storage/2018-11-09/blob/containers/lease_renew.go
@@ -0,0 +1,92 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// RenewLease renewes the lock based on the Lease ID
+func (client Client) RenewLease(ctx context.Context, accountName, containerName, leaseID string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "RenewLease", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "RenewLease", "`containerName` cannot be an empty string.")
+ }
+ if leaseID == "" {
+ return result, validation.NewError("containers.Client", "RenewLease", "`leaseID` cannot be an empty string.")
+ }
+
+ req, err := client.RenewLeasePreparer(ctx, accountName, containerName, leaseID)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "RenewLease", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.RenewLeaseSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "RenewLease", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.RenewLeaseResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "RenewLease", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// RenewLeasePreparer prepares the RenewLease request.
+func (client Client) RenewLeasePreparer(ctx context.Context, accountName string, containerName string, leaseID string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "container"),
+ "comp": autorest.Encode("path", "lease"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-lease-action": "renew",
+ "x-ms-lease-id": leaseID,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// RenewLeaseSender sends the RenewLease request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) RenewLeaseSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// RenewLeaseResponder handles the response to the RenewLease request. The method always
+// closes the http.Response Body.
+func (client Client) RenewLeaseResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/containers/lifecycle_test.go b/storage/2018-11-09/blob/containers/lifecycle_test.go
new file mode 100644
index 0000000..389c773
--- /dev/null
+++ b/storage/2018-11-09/blob/containers/lifecycle_test.go
@@ -0,0 +1,174 @@
+package containers
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestContainerLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ containerName := fmt.Sprintf("cont-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.BlobStorage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ containersClient := NewWithEnvironment(client.Environment)
+ containersClient.Client = client.PrepareWithAuthorizer(containersClient.Client, storageAuth)
+
+ // first let's test an empty container
+ input := CreateInput{}
+ _, err = containersClient.Create(ctx, accountName, containerName, input)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+
+ container, err := containersClient.GetProperties(ctx, accountName, containerName)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error retrieving: %s", err))
+ }
+
+ if container.AccessLevel != Private {
+ t.Fatalf("Expected Access Level to be Private but got %q", container.AccessLevel)
+ }
+ if len(container.MetaData) != 0 {
+ t.Fatalf("Expected MetaData to be empty but got: %s", container.MetaData)
+ }
+ if container.LeaseStatus != Unlocked {
+ t.Fatalf("Expected Container Lease to be Unlocked but was: %s", container.LeaseStatus)
+ }
+
+ // then update the metadata
+ metaData := map[string]string{
+ "dont": "kill-my-vibe",
+ }
+ _, err = containersClient.SetMetaData(ctx, accountName, containerName, metaData)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error updating metadata: %s", err))
+ }
+
+ // give azure time to replicate
+ time.Sleep(2 * time.Second)
+
+ // then assert that
+ container, err = containersClient.GetProperties(ctx, accountName, containerName)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error re-retrieving: %s", err))
+ }
+ if len(container.MetaData) != 1 {
+ t.Fatalf("Expected 1 item in the metadata but got: %s", container.MetaData)
+ }
+ if container.MetaData["dont"] != "kill-my-vibe" {
+ t.Fatalf("Expected `kill-my-vibe` but got %q", container.MetaData["dont"])
+ }
+ if container.AccessLevel != Private {
+ t.Fatalf("Expected Access Level to be Private but got %q", container.AccessLevel)
+ }
+ if container.LeaseStatus != Unlocked {
+ t.Fatalf("Expected Container Lease to be Unlocked but was: %s", container.LeaseStatus)
+ }
+
+ // then update the ACL
+ _, err = containersClient.SetAccessControl(ctx, accountName, containerName, Blob)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error updating ACL's: %s", err))
+ }
+
+ // give azure some time to replicate
+ time.Sleep(2 * time.Second)
+
+ // then assert that
+ container, err = containersClient.GetProperties(ctx, accountName, containerName)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error re-retrieving: %s", err))
+ }
+ if container.AccessLevel != Blob {
+ t.Fatalf("Expected Access Level to be Blob but got %q", container.AccessLevel)
+ }
+ if len(container.MetaData) != 1 {
+ t.Fatalf("Expected 1 item in the metadata but got: %s", container.MetaData)
+ }
+ if container.LeaseStatus != Unlocked {
+ t.Fatalf("Expected Container Lease to be Unlocked but was: %s", container.LeaseStatus)
+ }
+
+ // acquire a lease for 30s
+ acquireLeaseInput := AcquireLeaseInput{
+ LeaseDuration: 30,
+ }
+ acquireLeaseResp, err := containersClient.AcquireLease(ctx, accountName, containerName, acquireLeaseInput)
+ if err != nil {
+ t.Fatalf("Error acquiring lease: %s", err)
+ }
+ t.Logf("[DEBUG] Lease ID: %s", acquireLeaseResp.LeaseID)
+
+ // we should then be able to update the ID
+ t.Logf("[DEBUG] Changing lease..")
+ updateLeaseInput := ChangeLeaseInput{
+ ExistingLeaseID: acquireLeaseResp.LeaseID,
+ ProposedLeaseID: "aaaabbbb-aaaa-bbbb-cccc-aaaabbbbcccc",
+ }
+ updateLeaseResp, err := containersClient.ChangeLease(ctx, accountName, containerName, updateLeaseInput)
+ if err != nil {
+ t.Fatalf("Error changing lease: %s", err)
+ }
+
+ // then renew it
+ _, err = containersClient.RenewLease(ctx, accountName, containerName, updateLeaseResp.LeaseID)
+ if err != nil {
+ t.Fatalf("Error renewing lease: %s", err)
+ }
+
+ // and then give it a timeout
+ breakPeriod := 20
+ breakLeaseInput := BreakLeaseInput{
+ LeaseID: updateLeaseResp.LeaseID,
+ BreakPeriod: &breakPeriod,
+ }
+ breakLeaseResp, err := containersClient.BreakLease(ctx, accountName, containerName, breakLeaseInput)
+ if err != nil {
+ t.Fatalf("Error breaking lease: %s", err)
+ }
+ if breakLeaseResp.LeaseTime == 0 {
+ t.Fatalf("Lease broke immediately when should have waited: %d", breakLeaseResp.LeaseTime)
+ }
+
+ // and finally ditch it
+ _, err = containersClient.ReleaseLease(ctx, accountName, containerName, updateLeaseResp.LeaseID)
+ if err != nil {
+ t.Fatalf("Error releasing lease: %s", err)
+ }
+
+ t.Logf("[DEBUG] Listing blobs in the container..")
+ listInput := ListBlobsInput{}
+ listResult, err := containersClient.ListBlobs(ctx, accountName, containerName, listInput)
+ if err != nil {
+ t.Fatalf("Error listing blobs: %s", err)
+ }
+
+ if len(listResult.Blobs.Blobs) != 0 {
+ t.Fatalf("Expected there to be no blobs in the container but got %d", len(listResult.Blobs.Blobs))
+ }
+
+ t.Logf("[DEBUG] Deleting..")
+ _, err = containersClient.Delete(ctx, accountName, containerName)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error deleting: %s", err))
+ }
+}
diff --git a/storage/2018-11-09/blob/containers/list_blobs.go b/storage/2018-11-09/blob/containers/list_blobs.go
new file mode 100644
index 0000000..82797d0
--- /dev/null
+++ b/storage/2018-11-09/blob/containers/list_blobs.go
@@ -0,0 +1,179 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type ListBlobsInput struct {
+ Delimiter *string
+ Include *[]Dataset
+ Marker *string
+ MaxResults *int
+ Prefix *string
+}
+
+type ListBlobsResult struct {
+ autorest.Response
+
+ Delimiter string `xml:"Delimiter"`
+ Marker string `xml:"Marker"`
+ MaxResults int `xml:"MaxResults"`
+ NextMarker *string `xml:"NextMarker,omitempty"`
+ Prefix string `xml:"Prefix"`
+ Blobs Blobs `xml:"Blobs"`
+}
+
+type Blobs struct {
+ Blobs []BlobDetails `xml:"Blob"`
+ BlobPrefix *BlobPrefix `xml:"BlobPrefix"`
+}
+
+type BlobDetails struct {
+ Name string `xml:"Name"`
+ Deleted bool `xml:"Deleted,omitempty"`
+ MetaData map[string]interface{} `map:"Metadata,omitempty"`
+ Properties *BlobProperties `xml:"Properties,omitempty"`
+ Snapshot *string `xml:"Snapshot,omitempty"`
+}
+
+type BlobProperties struct {
+ AccessTier *string `xml:"AccessTier,omitempty"`
+ AccessTierInferred *bool `xml:"AccessTierInferred,omitempty"`
+ AccessTierChangeTime *string `xml:"AccessTierChangeTime,omitempty"`
+ BlobType *string `xml:"BlobType,omitempty"`
+ BlobSequenceNumber *string `xml:"x-ms-blob-sequence-number,omitempty"`
+ CacheControl *string `xml:"Cache-Control,omitempty"`
+ ContentEncoding *string `xml:"ContentEncoding,omitempty"`
+ ContentLanguage *string `xml:"Content-Language,omitempty"`
+ ContentLength *int64 `xml:"Content-Length,omitempty"`
+ ContentMD5 *string `xml:"Content-MD5,omitempty"`
+ ContentType *string `xml:"Content-Type,omitempty"`
+ CopyCompletionTime *string `xml:"CopyCompletionTime,omitempty"`
+ CopyId *string `xml:"CopyId,omitempty"`
+ CopyStatus *string `xml:"CopyStatus,omitempty"`
+ CopySource *string `xml:"CopySource,omitempty"`
+ CopyProgress *string `xml:"CopyProgress,omitempty"`
+ CopyStatusDescription *string `xml:"CopyStatusDescription,omitempty"`
+ CreationTime *string `xml:"CreationTime,omitempty"`
+ ETag *string `xml:"Etag,omitempty"`
+ DeletedTime *string `xml:"DeletedTime,omitempty"`
+ IncrementalCopy *bool `xml:"IncrementalCopy,omitempty"`
+ LastModified *string `xml:"Last-Modified,omitempty"`
+ LeaseDuration *string `xml:"LeaseDuration,omitempty"`
+ LeaseState *string `xml:"LeaseState,omitempty"`
+ LeaseStatus *string `xml:"LeaseStatus,omitempty"`
+ RemainingRetentionDays *string `xml:"RemainingRetentionDays,omitempty"`
+ ServerEncrypted *bool `xml:"ServerEncrypted,omitempty"`
+}
+
+type BlobPrefix struct {
+ Name string `xml:"Name"`
+}
+
+// ListBlobs lists the blobs matching the specified query within the specified Container
+func (client Client) ListBlobs(ctx context.Context, accountName, containerName string, input ListBlobsInput) (result ListBlobsResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "ListBlobs", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "ListBlobs", "`containerName` cannot be an empty string.")
+ }
+ if input.MaxResults != nil && (*input.MaxResults <= 0 || *input.MaxResults > 5000) {
+ return result, validation.NewError("containers.Client", "ListBlobs", "`input.MaxResults` can either be nil or between 0 and 5000.")
+ }
+
+ req, err := client.ListBlobsPreparer(ctx, accountName, containerName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "ListBlobs", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ListBlobsSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "ListBlobs", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ListBlobsResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "ListBlobs", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ListBlobsPreparer prepares the ListBlobs request.
+func (client Client) ListBlobsPreparer(ctx context.Context, accountName, containerName string, input ListBlobsInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "list"),
+ "restype": autorest.Encode("query", "container"),
+ }
+
+ if input.Delimiter != nil {
+ queryParameters["delimiter"] = autorest.Encode("query", *input.Delimiter)
+ }
+ if input.Include != nil {
+ vals := make([]string, 0)
+ for _, v := range *input.Include {
+ vals = append(vals, string(v))
+ }
+ include := strings.Join(vals, ",")
+ queryParameters["include"] = autorest.Encode("query", include)
+ }
+ if input.Marker != nil {
+ queryParameters["marker"] = autorest.Encode("query", *input.Marker)
+ }
+ if input.MaxResults != nil {
+ queryParameters["maxresults"] = autorest.Encode("query", *input.MaxResults)
+ }
+ if input.Prefix != nil {
+ queryParameters["prefix"] = autorest.Encode("query", *input.Prefix)
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ListBlobsSender sends the ListBlobs request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ListBlobsSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ListBlobsResponder handles the response to the ListBlobs request. The method always
+// closes the http.Response Body.
+func (client Client) ListBlobsResponder(resp *http.Response) (result ListBlobsResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/containers/models.go b/storage/2018-11-09/blob/containers/models.go
new file mode 100644
index 0000000..adba368
--- /dev/null
+++ b/storage/2018-11-09/blob/containers/models.go
@@ -0,0 +1,75 @@
+package containers
+
+import "github.com/Azure/go-autorest/autorest"
+
+type AccessLevel string
+
+var (
+ // Blob specifies public read access for blobs.
+ // Blob data within this container can be read via anonymous request,
+ // but container data is not available.
+ // Clients cannot enumerate blobs within the container via anonymous request.
+ Blob AccessLevel = "blob"
+
+ // Container specifies full public read access for container and blob data.
+ // Clients can enumerate blobs within the container via anonymous request,
+ // but cannot enumerate containers within the storage account.
+ Container AccessLevel = "container"
+
+ // Private specifies that container data is private to the account owner
+ Private AccessLevel = ""
+)
+
+type ContainerProperties struct {
+ autorest.Response
+
+ AccessLevel AccessLevel
+ LeaseStatus LeaseStatus
+ LeaseState LeaseState
+ LeaseDuration *LeaseDuration
+ MetaData map[string]string
+ HasImmutabilityPolicy bool
+ HasLegalHold bool
+}
+
+type Dataset string
+
+var (
+ Copy Dataset = "copy"
+ Deleted Dataset = "deleted"
+ MetaData Dataset = "metadata"
+ Snapshots Dataset = "snapshots"
+ UncommittedBlobs Dataset = "uncommittedblobs"
+)
+
+type ErrorResponse struct {
+ Code *string `xml:"Code"`
+ Message *string `xml:"Message"`
+}
+
+type LeaseDuration string
+
+var (
+ // If this lease is for a Fixed Duration
+ Fixed LeaseDuration = "fixed"
+
+ // If this lease is for an Indefinite Duration
+ Infinite LeaseDuration = "infinite"
+)
+
+type LeaseState string
+
+var (
+ Available LeaseState = "available"
+ Breaking LeaseState = "breaking"
+ Broken LeaseState = "broken"
+ Expired LeaseState = "expired"
+ Leased LeaseState = "leased"
+)
+
+type LeaseStatus string
+
+var (
+ Locked LeaseStatus = "locked"
+ Unlocked LeaseStatus = "unlocked"
+)
diff --git a/storage/2018-11-09/blob/containers/resource_id.go b/storage/2018-11-09/blob/containers/resource_id.go
new file mode 100644
index 0000000..a5bfd6e
--- /dev/null
+++ b/storage/2018-11-09/blob/containers/resource_id.go
@@ -0,0 +1,46 @@
+package containers
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Container
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, containerName string) string {
+ domain := endpoints.GetBlobEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s", domain, containerName)
+}
+
+type ResourceID struct {
+ AccountName string
+ ContainerName string
+}
+
+// ParseResourceID parses the Resource ID and returns an object which can be used
+// to interact with the Container Resource
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://foo.blob.core.windows.net/Bar
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ containerName := strings.TrimPrefix(uri.Path, "/")
+ return &ResourceID{
+ AccountName: *accountName,
+ ContainerName: containerName,
+ }, nil
+}
diff --git a/storage/2018-11-09/blob/containers/resource_id_test.go b/storage/2018-11-09/blob/containers/resource_id_test.go
new file mode 100644
index 0000000..e27bc9d
--- /dev/null
+++ b/storage/2018-11-09/blob/containers/resource_id_test.go
@@ -0,0 +1,79 @@
+package containers
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.blob.core.chinacloudapi.cn/container1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.blob.core.cloudapi.de/container1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.blob.core.windows.net/container1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.blob.core.usgovcloudapi.net/container1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "container1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.blob.core.chinacloudapi.cn/container1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.blob.core.cloudapi.de/container1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.blob.core.windows.net/container1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.blob.core.usgovcloudapi.net/container1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected the account name to be `account1` but got %q", actual.AccountName)
+ }
+
+ if actual.ContainerName != "container1" {
+ t.Fatalf("Expected the container name to be `container1` but got %q", actual.ContainerName)
+ }
+ }
+}
diff --git a/storage/2018-11-09/blob/containers/set_acl.go b/storage/2018-11-09/blob/containers/set_acl.go
new file mode 100644
index 0000000..fcf4e10
--- /dev/null
+++ b/storage/2018-11-09/blob/containers/set_acl.go
@@ -0,0 +1,100 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// SetAccessControl sets the Access Control for a Container without a Lease ID
+func (client Client) SetAccessControl(ctx context.Context, accountName, containerName string, level AccessLevel) (autorest.Response, error) {
+ return client.SetAccessControlWithLeaseID(ctx, accountName, containerName, "", level)
+}
+
+// SetAccessControlWithLeaseID sets the Access Control for a Container using the specified Lease ID
+func (client Client) SetAccessControlWithLeaseID(ctx context.Context, accountName, containerName, leaseID string, level AccessLevel) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "SetAccessControl", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "SetAccessControl", "`containerName` cannot be an empty string.")
+ }
+
+ req, err := client.SetAccessControlWithLeaseIDPreparer(ctx, accountName, containerName, leaseID, level)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "SetAccessControl", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetAccessControlWithLeaseIDSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "SetAccessControl", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetAccessControlWithLeaseIDResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "SetAccessControl", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetAccessControlWithLeaseIDPreparer prepares the SetAccessControlWithLeaseID request.
+func (client Client) SetAccessControlWithLeaseIDPreparer(ctx context.Context, accountName, containerName, leaseID string, level AccessLevel) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "acl"),
+ "restype": autorest.Encode("path", "container"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = client.setAccessLevelIntoHeaders(headers, level)
+
+ // If specified, Get Container Properties only succeeds if the container’s lease is active and matches this ID.
+ // If there is no active lease or the ID does not match, 412 (Precondition Failed) is returned.
+ if leaseID != "" {
+ headers["x-ms-lease-id"] = leaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetAccessControlWithLeaseIDSender sends the SetAccessControlWithLeaseID request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetAccessControlWithLeaseIDSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetAccessControlWithLeaseIDResponder handles the response to the SetAccessControlWithLeaseID request. The method always
+// closes the http.Response Body.
+func (client Client) SetAccessControlWithLeaseIDResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/containers/set_metadata.go b/storage/2018-11-09/blob/containers/set_metadata.go
new file mode 100644
index 0000000..fb9e07f
--- /dev/null
+++ b/storage/2018-11-09/blob/containers/set_metadata.go
@@ -0,0 +1,105 @@
+package containers
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// SetMetaData sets the specified MetaData on the Container without a Lease ID
+func (client Client) SetMetaData(ctx context.Context, accountName, containerName string, metaData map[string]string) (autorest.Response, error) {
+ return client.SetMetaDataWithLeaseID(ctx, accountName, containerName, "", metaData)
+}
+
+// SetMetaDataWithLeaseID sets the specified MetaData on the Container using the specified Lease ID
+func (client Client) SetMetaDataWithLeaseID(ctx context.Context, accountName, containerName, leaseID string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("containers.Client", "SetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if containerName == "" {
+ return result, validation.NewError("containers.Client", "SetMetaData", "`containerName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("containers.Client", "SetMetaData", fmt.Sprintf("`metaData` is not valid: %s.", err))
+ }
+
+ req, err := client.SetMetaDataWithLeaseIDPreparer(ctx, accountName, containerName, leaseID, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "SetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetMetaDataWithLeaseIDSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "containers.Client", "SetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetMetaDataWithLeaseIDResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "containers.Client", "SetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetMetaDataWithLeaseIDPreparer prepares the SetMetaDataWithLeaseID request.
+func (client Client) SetMetaDataWithLeaseIDPreparer(ctx context.Context, accountName, containerName, leaseID string, metaData map[string]string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "containerName": autorest.Encode("path", containerName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "metadata"),
+ "restype": autorest.Encode("path", "container"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ // If specified, Get Container Properties only succeeds if the container’s lease is active and matches this ID.
+ // If there is no active lease or the ID does not match, 412 (Precondition Failed) is returned.
+ if leaseID != "" {
+ headers["x-ms-lease-id"] = leaseID
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetBlobEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{containerName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetMetaDataWithLeaseIDSender sends the SetMetaDataWithLeaseID request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetMetaDataWithLeaseIDSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetMetaDataWithLeaseIDResponder handles the response to the SetMetaDataWithLeaseID request. The method always
+// closes the http.Response Body.
+func (client Client) SetMetaDataWithLeaseIDResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/blob/containers/version.go b/storage/2018-11-09/blob/containers/version.go
new file mode 100644
index 0000000..7047f30
--- /dev/null
+++ b/storage/2018-11-09/blob/containers/version.go
@@ -0,0 +1,14 @@
+package containers
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2018-11-09"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2018-11-09/file/directories/README.md b/storage/2018-11-09/file/directories/README.md
new file mode 100644
index 0000000..cd120aa
--- /dev/null
+++ b/storage/2018-11-09/file/directories/README.md
@@ -0,0 +1,44 @@
+## File Storage Directories SDK for API version 2018-11-09
+
+This package allows you to interact with the Directories File Storage API
+
+### Supported Authorizers
+
+* Azure Active Directory (for the Resource Endpoint `https://storage.azure.com`)
+* SharedKeyLite (Blob, File & Queue)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/directories"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ shareName := "myshare"
+ directoryName := "myfiles"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ directoriesClient := directories.New()
+ directoriesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ metadata := map[string]string{
+ "hello": "world",
+ }
+ if _, err := directoriesClient.Create(ctx, accountName, shareName, directoryName, metadata); err != nil {
+ return fmt.Errorf("Error creating Directory: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2018-11-09/file/directories/client.go b/storage/2018-11-09/file/directories/client.go
new file mode 100644
index 0000000..bf2d315
--- /dev/null
+++ b/storage/2018-11-09/file/directories/client.go
@@ -0,0 +1,25 @@
+package directories
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for File Storage Shares.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2018-11-09/file/directories/create.go b/storage/2018-11-09/file/directories/create.go
new file mode 100644
index 0000000..93f5c82
--- /dev/null
+++ b/storage/2018-11-09/file/directories/create.go
@@ -0,0 +1,101 @@
+package directories
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// Create creates a new directory under the specified share or parent directory.
+func (client Client) Create(ctx context.Context, accountName, shareName, path string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("directories.Client", "Create", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("directories.Client", "Create", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("directories.Client", "Create", "`shareName` must be a lower-cased string.")
+ }
+ if path == "" {
+ return result, validation.NewError("directories.Client", "Create", "`path` cannot be an empty string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("directories.Client", "Create", fmt.Sprintf("`metadata` is not valid: %s.", err))
+ }
+
+ req, err := client.CreatePreparer(ctx, accountName, shareName, path, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "Create", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "directories.Client", "Create", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "Create", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreatePreparer prepares the Create request.
+func (client Client) CreatePreparer(ctx context.Context, accountName, shareName, path string, metaData map[string]string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "directory"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSender sends the Create request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateResponder handles the response to the Create request. The method always
+// closes the http.Response Body.
+func (client Client) CreateResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/directories/delete.go b/storage/2018-11-09/file/directories/delete.go
new file mode 100644
index 0000000..9443c25
--- /dev/null
+++ b/storage/2018-11-09/file/directories/delete.go
@@ -0,0 +1,95 @@
+package directories
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete removes the specified empty directory
+// Note that the directory must be empty before it can be deleted.
+func (client Client) Delete(ctx context.Context, accountName, shareName, path string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("directories.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("directories.Client", "Delete", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("directories.Client", "Delete", "`shareName` must be a lower-cased string.")
+ }
+ if path == "" {
+ return result, validation.NewError("directories.Client", "Delete", "`path` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, shareName, path)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "directories.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, shareName, path string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "directory"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/directories/get.go b/storage/2018-11-09/file/directories/get.go
new file mode 100644
index 0000000..817d680
--- /dev/null
+++ b/storage/2018-11-09/file/directories/get.go
@@ -0,0 +1,112 @@
+package directories
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetResult struct {
+ autorest.Response
+
+ // A set of name-value pairs that contain metadata for the directory.
+ MetaData map[string]string
+
+ // The value of this header is set to true if the directory metadata is completely
+ // encrypted using the specified algorithm. Otherwise, the value is set to false.
+ DirectoryMetaDataEncrypted bool
+}
+
+// Get returns all system properties for the specified directory,
+// and can also be used to check the existence of a directory.
+func (client Client) Get(ctx context.Context, accountName, shareName, path string) (result GetResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("directories.Client", "Get", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("directories.Client", "Get", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("directories.Client", "Get", "`shareName` must be a lower-cased string.")
+ }
+ if path == "" {
+ return result, validation.NewError("directories.Client", "Get", "`path` cannot be an empty string.")
+ }
+
+ req, err := client.GetPreparer(ctx, accountName, shareName, path)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "Get", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "directories.Client", "Get", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "Get", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPreparer prepares the Get request.
+func (client Client) GetPreparer(ctx context.Context, accountName, shareName, path string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "directory"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetSender sends the Get request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetResponder handles the response to the Get request. The method always
+// closes the http.Response Body.
+func (client Client) GetResponder(resp *http.Response) (result GetResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ result.DirectoryMetaDataEncrypted = strings.EqualFold(resp.Header.Get("x-ms-server-encrypted"), "true")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/directories/lifecycle_test.go b/storage/2018-11-09/file/directories/lifecycle_test.go
new file mode 100644
index 0000000..fb1bc28
--- /dev/null
+++ b/storage/2018-11-09/file/directories/lifecycle_test.go
@@ -0,0 +1,107 @@
+package directories
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/shares"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestDirectoriesLifeCycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := shares.NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ directoriesClient := NewWithEnvironment(client.Environment)
+ directoriesClient.Client = client.PrepareWithAuthorizer(directoriesClient.Client, storageAuth)
+
+ input := shares.CreateInput{
+ QuotaInGB: 1,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+ defer sharesClient.Delete(ctx, accountName, shareName, true)
+
+ metaData := map[string]string{
+ "hello": "world",
+ }
+
+ log.Printf("[DEBUG] Creating Top Level..")
+ if _, err := directoriesClient.Create(ctx, accountName, shareName, "hello", metaData); err != nil {
+ t.Fatalf("Error creating Top Level Directory: %s", err)
+ }
+
+ log.Printf("[DEBUG] Creating Inner..")
+ if _, err := directoriesClient.Create(ctx, accountName, shareName, "hello/there", metaData); err != nil {
+ t.Fatalf("Error creating Inner Directory: %s", err)
+ }
+
+ log.Printf("[DEBUG] Retrieving share")
+ innerDir, err := directoriesClient.Get(ctx, accountName, shareName, "hello/there")
+ if err != nil {
+ t.Fatalf("Error retrieving Inner Directory: %s", err)
+ }
+
+ if innerDir.DirectoryMetaDataEncrypted != true {
+ t.Fatalf("Expected MetaData to be encrypted but got: %t", innerDir.DirectoryMetaDataEncrypted)
+ }
+
+ if len(innerDir.MetaData) != 1 {
+ t.Fatalf("Expected MetaData to contain 1 item but got %d", len(innerDir.MetaData))
+ }
+ if innerDir.MetaData["hello"] != "world" {
+ t.Fatalf("Expected MetaData `hello` to be `world`: %s", innerDir.MetaData["hello"])
+ }
+
+ log.Printf("[DEBUG] Setting MetaData")
+ updatedMetaData := map[string]string{
+ "panda": "pops",
+ }
+ if _, err := directoriesClient.SetMetaData(ctx, accountName, shareName, "hello/there", updatedMetaData); err != nil {
+ t.Fatalf("Error updating MetaData: %s", err)
+ }
+
+ log.Printf("[DEBUG] Retrieving MetaData")
+ retrievedMetaData, err := directoriesClient.GetMetaData(ctx, accountName, shareName, "hello/there")
+ if err != nil {
+ t.Fatalf("Error retrieving the updated metadata: %s", err)
+ }
+ if len(retrievedMetaData.MetaData) != 1 {
+ t.Fatalf("Expected the updated metadata to have 1 item but got %d", len(retrievedMetaData.MetaData))
+ }
+ if retrievedMetaData.MetaData["panda"] != "pops" {
+ t.Fatalf("Expected the metadata `panda` to be `pops` but got %q", retrievedMetaData.MetaData["panda"])
+ }
+
+ t.Logf("[DEBUG] Deleting Inner..")
+ if _, err := directoriesClient.Delete(ctx, accountName, shareName, "hello/there"); err != nil {
+ t.Fatalf("Error deleting Inner Directory: %s", err)
+ }
+
+ t.Logf("[DEBUG] Deleting Top Level..")
+ if _, err := directoriesClient.Delete(ctx, accountName, shareName, "hello"); err != nil {
+ t.Fatalf("Error deleting Top Level Directory: %s", err)
+ }
+}
diff --git a/storage/2018-11-09/file/directories/metadata_get.go b/storage/2018-11-09/file/directories/metadata_get.go
new file mode 100644
index 0000000..173716d
--- /dev/null
+++ b/storage/2018-11-09/file/directories/metadata_get.go
@@ -0,0 +1,106 @@
+package directories
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetMetaDataResult struct {
+ autorest.Response
+
+ MetaData map[string]string
+}
+
+// GetMetaData returns all user-defined metadata for the specified directory
+func (client Client) GetMetaData(ctx context.Context, accountName, shareName, path string) (result GetMetaDataResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("directories.Client", "GetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("directories.Client", "GetMetaData", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("directories.Client", "GetMetaData", "`shareName` must be a lower-cased string.")
+ }
+ if path == "" {
+ return result, validation.NewError("directories.Client", "GetMetaData", "`path` cannot be an empty string.")
+ }
+
+ req, err := client.GetMetaDataPreparer(ctx, accountName, shareName, path)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "GetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetMetaDataSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "directories.Client", "GetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "GetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetMetaDataPreparer prepares the GetMetaData request.
+func (client Client) GetMetaDataPreparer(ctx context.Context, accountName, shareName, path string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "directory"),
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetMetaDataSender sends the GetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetMetaDataResponder handles the response to the GetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) GetMetaDataResponder(resp *http.Response) (result GetMetaDataResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/directories/metadata_set.go b/storage/2018-11-09/file/directories/metadata_set.go
new file mode 100644
index 0000000..cb13312
--- /dev/null
+++ b/storage/2018-11-09/file/directories/metadata_set.go
@@ -0,0 +1,102 @@
+package directories
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// SetMetaData updates user defined metadata for the specified directory
+func (client Client) SetMetaData(ctx context.Context, accountName, shareName, path string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("directories.Client", "SetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("directories.Client", "SetMetaData", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("directories.Client", "SetMetaData", "`shareName` must be a lower-cased string.")
+ }
+ if path == "" {
+ return result, validation.NewError("directories.Client", "SetMetaData", "`path` cannot be an empty string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("directories.Client", "SetMetaData", fmt.Sprintf("`metaData` is not valid: %s.", err))
+ }
+
+ req, err := client.SetMetaDataPreparer(ctx, accountName, shareName, path, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "SetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetMetaDataSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "directories.Client", "SetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "directories.Client", "SetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetMetaDataPreparer prepares the SetMetaData request.
+func (client Client) SetMetaDataPreparer(ctx context.Context, accountName, shareName, path string, metaData map[string]string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "directory"),
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetMetaDataSender sends the SetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetMetaDataResponder handles the response to the SetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) SetMetaDataResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/directories/resource_id.go b/storage/2018-11-09/file/directories/resource_id.go
new file mode 100644
index 0000000..44607c4
--- /dev/null
+++ b/storage/2018-11-09/file/directories/resource_id.go
@@ -0,0 +1,56 @@
+package directories
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Directory
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, shareName, directoryName string) string {
+ domain := endpoints.GetFileEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s/%s", domain, shareName, directoryName)
+}
+
+type ResourceID struct {
+ AccountName string
+ DirectoryName string
+ ShareName string
+}
+
+// ParseResourceID parses the Resource ID into an Object
+// which can be used to interact with the Directory within the File Share
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://foo.file.core.windows.net/Bar/Folder
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ path := strings.TrimPrefix(uri.Path, "/")
+ segments := strings.Split(path, "/")
+ if len(segments) == 0 {
+ return nil, fmt.Errorf("Expected the path to contain segments but got none")
+ }
+
+ shareName := segments[0]
+ directoryName := strings.TrimPrefix(path, shareName)
+ directoryName = strings.TrimPrefix(directoryName, "/")
+ return &ResourceID{
+ AccountName: *accountName,
+ ShareName: shareName,
+ DirectoryName: directoryName,
+ }, nil
+}
diff --git a/storage/2018-11-09/file/directories/resource_id_test.go b/storage/2018-11-09/file/directories/resource_id_test.go
new file mode 100644
index 0000000..0be800d
--- /dev/null
+++ b/storage/2018-11-09/file/directories/resource_id_test.go
@@ -0,0 +1,81 @@
+package directories
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.file.core.chinacloudapi.cn/share1/directory1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.file.core.cloudapi.de/share1/directory1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.file.core.windows.net/share1/directory1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.file.core.usgovcloudapi.net/share1/directory1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "share1", "directory1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.file.core.chinacloudapi.cn/share1/directory1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.file.core.cloudapi.de/share1/directory1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.file.core.windows.net/share1/directory1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.file.core.usgovcloudapi.net/share1/directory1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.ShareName != "share1" {
+ t.Fatalf("Expected Share Name to be `share1` but got %q", actual.ShareName)
+ }
+ if actual.DirectoryName != "directory1" {
+ t.Fatalf("Expected Directory Name to be `directory1` but got %q", actual.DirectoryName)
+ }
+ }
+}
diff --git a/storage/2018-11-09/file/directories/version.go b/storage/2018-11-09/file/directories/version.go
new file mode 100644
index 0000000..6e8fb25
--- /dev/null
+++ b/storage/2018-11-09/file/directories/version.go
@@ -0,0 +1,14 @@
+package directories
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2018-11-09"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2018-11-09/file/files/README.md b/storage/2018-11-09/file/files/README.md
new file mode 100644
index 0000000..19b7af7
--- /dev/null
+++ b/storage/2018-11-09/file/files/README.md
@@ -0,0 +1,43 @@
+## File Storage Files SDK for API version 2018-11-09
+
+This package allows you to interact with the Files File Storage API
+
+### Supported Authorizers
+
+* Azure Active Directory (for the Resource Endpoint `https://storage.azure.com`)
+* SharedKeyLite (Blob, File & Queue)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/files"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ shareName := "myshare"
+ directoryName := "myfiles"
+ fileName := "example.txt"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ filesClient := files.New()
+ filesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ input := files.CreateInput{}
+ if _, err := filesClient.Create(ctx, accountName, shareName, directoryName, fileName, input); err != nil {
+ return fmt.Errorf("Error creating File: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2018-11-09/file/files/client.go b/storage/2018-11-09/file/files/client.go
new file mode 100644
index 0000000..ecca815
--- /dev/null
+++ b/storage/2018-11-09/file/files/client.go
@@ -0,0 +1,25 @@
+package files
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for File Storage Shares.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2018-11-09/file/files/copy.go b/storage/2018-11-09/file/files/copy.go
new file mode 100644
index 0000000..31768b3
--- /dev/null
+++ b/storage/2018-11-09/file/files/copy.go
@@ -0,0 +1,132 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type CopyInput struct {
+ // Specifies the URL of the source file or blob, up to 2 KB in length.
+ //
+ // To copy a file to another file within the same storage account, you may use Shared Key to authenticate
+ // the source file. If you are copying a file from another storage account, or if you are copying a blob from
+ // the same storage account or another storage account, then you must authenticate the source file or blob using a
+ // shared access signature. If the source is a public blob, no authentication is required to perform the copy
+ // operation. A file in a share snapshot can also be specified as a copy source.
+ CopySource string
+
+ MetaData map[string]string
+}
+
+type CopyResult struct {
+ autorest.Response
+
+ // The CopyID, which can be passed to AbortCopy to abort the copy.
+ CopyID string
+
+ // Either `success` or `pending`
+ CopySuccess string
+}
+
+// Copy copies a blob or file to a destination file within the storage account asynchronously.
+func (client Client) Copy(ctx context.Context, accountName, shareName, path, fileName string, input CopyInput) (result CopyResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "Copy", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "Copy", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "Copy", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "Copy", "`fileName` cannot be an empty string.")
+ }
+ if input.CopySource == "" {
+ return result, validation.NewError("files.Client", "Copy", "`input.CopySource` cannot be an empty string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("files.Client", "Copy", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.CopyPreparer(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "Copy", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CopySender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "Copy", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CopyResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "Copy", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CopyPreparer prepares the Copy request.
+func (client Client) CopyPreparer(ctx context.Context, accountName, shareName, path, fileName string, input CopyInput) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-copy-source": input.CopySource,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CopySender sends the Copy request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CopySender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CopyResponder handles the response to the Copy request. The method always
+// closes the http.Response Body.
+func (client Client) CopyResponder(resp *http.Response) (result CopyResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.CopyID = resp.Header.Get("x-ms-copy-id")
+ result.CopySuccess = resp.Header.Get("x-ms-copy-status")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/files/copy_abort.go b/storage/2018-11-09/file/files/copy_abort.go
new file mode 100644
index 0000000..2f09131
--- /dev/null
+++ b/storage/2018-11-09/file/files/copy_abort.go
@@ -0,0 +1,104 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// AbortCopy aborts a pending Copy File operation, and leaves a destination file with zero length and full metadata
+func (client Client) AbortCopy(ctx context.Context, accountName, shareName, path, fileName, copyID string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "AbortCopy", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "AbortCopy", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "AbortCopy", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "AbortCopy", "`fileName` cannot be an empty string.")
+ }
+ if copyID == "" {
+ return result, validation.NewError("files.Client", "AbortCopy", "`copyID` cannot be an empty string.")
+ }
+
+ req, err := client.AbortCopyPreparer(ctx, accountName, shareName, path, fileName, copyID)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "AbortCopy", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.AbortCopySender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "AbortCopy", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.AbortCopyResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "AbortCopy", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// AbortCopyPreparer prepares the AbortCopy request.
+func (client Client) AbortCopyPreparer(ctx context.Context, accountName, shareName, path, fileName, copyID string) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "copy"),
+ "copyid": autorest.Encode("query", copyID),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-copy-action": "abort",
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// AbortCopySender sends the AbortCopy request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) AbortCopySender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// AbortCopyResponder handles the response to the AbortCopy request. The method always
+// closes the http.Response Body.
+func (client Client) AbortCopyResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/files/copy_wait.go b/storage/2018-11-09/file/files/copy_wait.go
new file mode 100644
index 0000000..e6a646b
--- /dev/null
+++ b/storage/2018-11-09/file/files/copy_wait.go
@@ -0,0 +1,55 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+)
+
+type CopyAndWaitResult struct {
+ autorest.Response
+
+ CopyID string
+}
+
+const DefaultCopyPollDuration = 15 * time.Second
+
+// CopyAndWait is a convenience method which doesn't exist in the API, which copies the file and then waits for the copy to complete
+func (client Client) CopyAndWait(ctx context.Context, accountName, shareName, path, fileName string, input CopyInput, pollDuration time.Duration) (result CopyResult, err error) {
+ copy, e := client.Copy(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ result.Response = copy.Response
+ err = fmt.Errorf("Error copying: %s", e)
+ return
+ }
+
+ result.CopyID = copy.CopyID
+
+ // since the API doesn't return a LRO, this is a hack which also polls every 10s, but should be sufficient
+ for true {
+ props, e := client.GetProperties(ctx, accountName, shareName, path, fileName)
+ if e != nil {
+ result.Response = copy.Response
+ err = fmt.Errorf("Error waiting for copy: %s", e)
+ return
+ }
+
+ switch strings.ToLower(props.CopyStatus) {
+ case "pending":
+ time.Sleep(pollDuration)
+ continue
+
+ case "success":
+ return
+
+ default:
+ err = fmt.Errorf("Unexpected CopyState %q", e)
+ return
+ }
+ }
+
+ return
+}
diff --git a/storage/2018-11-09/file/files/copy_wait_test.go b/storage/2018-11-09/file/files/copy_wait_test.go
new file mode 100644
index 0000000..95f1ed0
--- /dev/null
+++ b/storage/2018-11-09/file/files/copy_wait_test.go
@@ -0,0 +1,129 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/shares"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestFilesCopyAndWaitFromURL(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := shares.NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ input := shares.CreateInput{
+ QuotaInGB: 10,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+ defer sharesClient.Delete(ctx, accountName, shareName, false)
+
+ filesClient := NewWithEnvironment(client.Environment)
+ filesClient.Client = client.PrepareWithAuthorizer(filesClient.Client, storageAuth)
+
+ copiedFileName := "ubuntu.iso"
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+
+ t.Logf("[DEBUG] Copy And Waiting..")
+ if _, err := filesClient.CopyAndWait(ctx, accountName, shareName, "", copiedFileName, copyInput, DefaultCopyPollDuration); err != nil {
+ t.Fatalf("Error copy & waiting: %s", err)
+ }
+
+ t.Logf("[DEBUG] Asserting that the file's ready..")
+
+ props, err := filesClient.GetProperties(ctx, accountName, shareName, "", copiedFileName)
+ if err != nil {
+ t.Fatalf("Error retrieving file: %s", err)
+ }
+
+ if !strings.EqualFold(props.CopyStatus, "success") {
+ t.Fatalf("Expected the Copy Status to be `Success` but got %q", props.CopyStatus)
+ }
+}
+
+func TestFilesCopyAndWaitFromBlob(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := shares.NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ input := shares.CreateInput{
+ QuotaInGB: 10,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+ defer sharesClient.Delete(ctx, accountName, shareName, false)
+
+ filesClient := NewWithEnvironment(client.Environment)
+ filesClient.Client = client.PrepareWithAuthorizer(filesClient.Client, storageAuth)
+
+ originalFileName := "ubuntu.iso"
+ copiedFileName := "ubuntu-copied.iso"
+ copyInput := CopyInput{
+ CopySource: "http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso",
+ }
+ t.Logf("[DEBUG] Copy And Waiting the original file..")
+ if _, err := filesClient.CopyAndWait(ctx, accountName, shareName, "", originalFileName, copyInput, DefaultCopyPollDuration); err != nil {
+ t.Fatalf("Error copy & waiting: %s", err)
+ }
+
+ t.Logf("[DEBUG] Now copying that blob..")
+ duplicateInput := CopyInput{
+ CopySource: fmt.Sprintf("%s/%s/%s", endpoints.GetFileEndpoint(filesClient.BaseURI, accountName), shareName, originalFileName),
+ }
+ if _, err := filesClient.CopyAndWait(ctx, accountName, shareName, "", copiedFileName, duplicateInput, DefaultCopyPollDuration); err != nil {
+ t.Fatalf("Error copying duplicate: %s", err)
+ }
+
+ t.Logf("[DEBUG] Asserting that the file's ready..")
+ props, err := filesClient.GetProperties(ctx, accountName, shareName, "", copiedFileName)
+ if err != nil {
+ t.Fatalf("Error retrieving file: %s", err)
+ }
+
+ if !strings.EqualFold(props.CopyStatus, "success") {
+ t.Fatalf("Expected the Copy Status to be `Success` but got %q", props.CopyStatus)
+ }
+}
diff --git a/storage/2018-11-09/file/files/create.go b/storage/2018-11-09/file/files/create.go
new file mode 100644
index 0000000..85d4b0b
--- /dev/null
+++ b/storage/2018-11-09/file/files/create.go
@@ -0,0 +1,146 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type CreateInput struct {
+ // This header specifies the maximum size for the file, up to 1 TiB.
+ ContentLength int64
+
+ // The MIME content type of the file
+ // If not specified, the default type is application/octet-stream.
+ ContentType *string
+
+ // Specifies which content encodings have been applied to the file.
+ // This value is returned to the client when the Get File operation is performed
+ // on the file resource and can be used to decode file content.
+ ContentEncoding *string
+
+ // Specifies the natural languages used by this resource.
+ ContentLanguage *string
+
+ // The File service stores this value but does not use or modify it.
+ CacheControl *string
+
+ // Sets the file's MD5 hash.
+ ContentMD5 *string
+
+ // Sets the file’s Content-Disposition header.
+ ContentDisposition *string
+
+ MetaData map[string]string
+}
+
+// Create creates a new file or replaces a file.
+func (client Client) Create(ctx context.Context, accountName, shareName, path, fileName string, input CreateInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "Create", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "Create", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "Create", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "Create", "`fileName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("files.Client", "Create", "`input.MetaData` cannot be an empty string.")
+ }
+
+ req, err := client.CreatePreparer(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "Create", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "Create", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "Create", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreatePreparer prepares the Create request.
+func (client Client) CreatePreparer(ctx context.Context, accountName, shareName, path, fileName string, input CreateInput) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-content-length": input.ContentLength,
+ "x-ms-type": "file",
+ }
+
+ if input.ContentDisposition != nil {
+ headers["x-ms-content-disposition"] = *input.ContentDisposition
+ }
+
+ if input.ContentEncoding != nil {
+ headers["x-ms-content-encoding"] = *input.ContentEncoding
+ }
+
+ if input.ContentMD5 != nil {
+ headers["x-ms-content-md5"] = *input.ContentMD5
+ }
+
+ if input.ContentType != nil {
+ headers["x-ms-content-type"] = *input.ContentType
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSender sends the Create request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateResponder handles the response to the Create request. The method always
+// closes the http.Response Body.
+func (client Client) CreateResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/files/delete.go b/storage/2018-11-09/file/files/delete.go
new file mode 100644
index 0000000..5debd76
--- /dev/null
+++ b/storage/2018-11-09/file/files/delete.go
@@ -0,0 +1,94 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete immediately deletes the file from the File Share.
+func (client Client) Delete(ctx context.Context, accountName, shareName, path, fileName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "Delete", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "Delete", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "Delete", "`fileName` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, shareName, path, fileName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, shareName, path, fileName string) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/files/lifecycle_test.go b/storage/2018-11-09/file/files/lifecycle_test.go
new file mode 100644
index 0000000..8b2578a
--- /dev/null
+++ b/storage/2018-11-09/file/files/lifecycle_test.go
@@ -0,0 +1,144 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/shares"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestFilesLifeCycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := shares.NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ input := shares.CreateInput{
+ QuotaInGB: 1,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+ defer sharesClient.Delete(ctx, accountName, shareName, false)
+
+ filesClient := NewWithEnvironment(client.Environment)
+ filesClient.Client = client.PrepareWithAuthorizer(filesClient.Client, storageAuth)
+
+ fileName := "bled5.png"
+ contentEncoding := "application/vnd+panda"
+
+ t.Logf("[DEBUG] Creating Top Level File..")
+ createInput := CreateInput{
+ ContentLength: 1024,
+ ContentEncoding: &contentEncoding,
+ }
+ if _, err := filesClient.Create(ctx, accountName, shareName, "", fileName, createInput); err != nil {
+ t.Fatalf("Error creating Top-Level File: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving Properties for the Top-Level File..")
+ file, err := filesClient.GetProperties(ctx, accountName, shareName, "", fileName)
+ if err != nil {
+ t.Fatalf("Error retrieving Top-Level File: %s", err)
+ }
+
+ if *file.ContentLength != 1024 {
+ t.Fatalf("Expected the Content-Length to be 1024 but got %d", *file.ContentLength)
+ }
+
+ if file.ContentEncoding != contentEncoding {
+ t.Fatalf("Expected the Content-Encoding to be %q but got %q", contentEncoding, file.ContentEncoding)
+ }
+
+ updatedSize := int64(2048)
+ updatedEncoding := "application/vnd+pandas2"
+ updatedInput := SetPropertiesInput{
+ ContentEncoding: &updatedEncoding,
+ ContentLength: &updatedSize,
+ }
+ if _, err := filesClient.SetProperties(ctx, accountName, shareName, "", fileName, updatedInput); err != nil {
+ t.Fatalf("Error setting properties: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-retrieving Properties for the Top-Level File..")
+ file, err = filesClient.GetProperties(ctx, accountName, shareName, "", fileName)
+ if err != nil {
+ t.Fatalf("Error retrieving Top-Level File: %s", err)
+ }
+
+ if *file.ContentLength != 2048 {
+ t.Fatalf("Expected the Content-Length to be 1024 but got %d", *file.ContentLength)
+ }
+
+ if file.ContentEncoding != updatedEncoding {
+ t.Fatalf("Expected the Content-Encoding to be %q but got %q", updatedEncoding, file.ContentEncoding)
+ }
+
+ t.Logf("[DEBUG] Setting MetaData..")
+ metaData := map[string]string{
+ "hello": "there",
+ }
+ if _, err := filesClient.SetMetaData(ctx, accountName, shareName, "", fileName, metaData); err != nil {
+ t.Fatalf("Error setting MetaData: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving MetaData..")
+ retrievedMetaData, err := filesClient.GetMetaData(ctx, accountName, shareName, "", fileName)
+ if err != nil {
+ t.Fatalf("Error retrieving MetaData: %s", err)
+ }
+ if len(retrievedMetaData.MetaData) != 1 {
+ t.Fatalf("Expected 1 item but got %d", len(retrievedMetaData.MetaData))
+ }
+ if retrievedMetaData.MetaData["hello"] != "there" {
+ t.Fatalf("Expected `hello` to be `there` but got %q", retrievedMetaData.MetaData["hello"])
+ }
+
+ t.Logf("[DEBUG] Re-Setting MetaData..")
+ metaData = map[string]string{
+ "hello": "there",
+ "second": "thing",
+ }
+ if _, err := filesClient.SetMetaData(ctx, accountName, shareName, "", fileName, metaData); err != nil {
+ t.Fatalf("Error setting MetaData: %s", err)
+ }
+
+ t.Logf("[DEBUG] Re-Retrieving MetaData..")
+ retrievedMetaData, err = filesClient.GetMetaData(ctx, accountName, shareName, "", fileName)
+ if err != nil {
+ t.Fatalf("Error retrieving MetaData: %s", err)
+ }
+ if len(retrievedMetaData.MetaData) != 2 {
+ t.Fatalf("Expected 2 items but got %d", len(retrievedMetaData.MetaData))
+ }
+ if retrievedMetaData.MetaData["hello"] != "there" {
+ t.Fatalf("Expected `hello` to be `there` but got %q", retrievedMetaData.MetaData["hello"])
+ }
+ if retrievedMetaData.MetaData["second"] != "thing" {
+ t.Fatalf("Expected `second` to be `thing` but got %q", retrievedMetaData.MetaData["second"])
+ }
+
+ t.Logf("[DEBUG] Deleting Top Level File..")
+ if _, err := filesClient.Delete(ctx, accountName, shareName, "", fileName); err != nil {
+ t.Fatalf("Error deleting Top-Level File: %s", err)
+ }
+}
diff --git a/storage/2018-11-09/file/files/metadata_get.go b/storage/2018-11-09/file/files/metadata_get.go
new file mode 100644
index 0000000..fd62f90
--- /dev/null
+++ b/storage/2018-11-09/file/files/metadata_get.go
@@ -0,0 +1,111 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetMetaDataResult struct {
+ autorest.Response
+
+ MetaData map[string]string
+}
+
+// GetMetaData returns the MetaData for the specified File.
+func (client Client) GetMetaData(ctx context.Context, accountName, shareName, path, fileName string) (result GetMetaDataResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "GetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "GetMetaData", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "GetMetaData", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "GetMetaData", "`fileName` cannot be an empty string.")
+ }
+
+ req, err := client.GetMetaDataPreparer(ctx, accountName, shareName, path, fileName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "GetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetMetaDataSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "GetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "GetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetMetaDataPreparer prepares the GetMetaData request.
+func (client Client) GetMetaDataPreparer(ctx context.Context, accountName, shareName, path, fileName string) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetMetaDataSender sends the GetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetMetaDataResponder handles the response to the GetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) GetMetaDataResponder(resp *http.Response) (result GetMetaDataResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ //metadata.ByParsingFromHeaders(&result.MetaData),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/files/metadata_set.go b/storage/2018-11-09/file/files/metadata_set.go
new file mode 100644
index 0000000..41e3ffc
--- /dev/null
+++ b/storage/2018-11-09/file/files/metadata_set.go
@@ -0,0 +1,105 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// SetMetaData updates the specified File to have the specified MetaData.
+func (client Client) SetMetaData(ctx context.Context, accountName, shareName, path, fileName string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "SetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "SetMetaData", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "SetMetaData", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "SetMetaData", "`fileName` cannot be an empty string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("files.Client", "SetMetaData", fmt.Sprintf("`metaData` is not valid: %s.", err))
+ }
+
+ req, err := client.SetMetaDataPreparer(ctx, accountName, shareName, path, fileName, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "SetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetMetaDataSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "SetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "SetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetMetaDataPreparer prepares the SetMetaData request.
+func (client Client) SetMetaDataPreparer(ctx context.Context, accountName, shareName, path, fileName string, metaData map[string]string) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetMetaDataSender sends the SetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetMetaDataResponder handles the response to the SetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) SetMetaDataResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/files/properties_get.go b/storage/2018-11-09/file/files/properties_get.go
new file mode 100644
index 0000000..c6a0c39
--- /dev/null
+++ b/storage/2018-11-09/file/files/properties_get.go
@@ -0,0 +1,144 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetResult struct {
+ autorest.Response
+
+ CacheControl string
+ ContentDisposition string
+ ContentEncoding string
+ ContentLanguage string
+ ContentLength *int64
+ ContentMD5 string
+ ContentType string
+ CopyID string
+ CopyStatus string
+ CopySource string
+ CopyProgress string
+ CopyStatusDescription string
+ CopyCompletionTime string
+ Encrypted bool
+
+ MetaData map[string]string
+}
+
+// GetProperties returns the Properties for the specified file
+func (client Client) GetProperties(ctx context.Context, accountName, shareName, path, fileName string) (result GetResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "GetProperties", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "GetProperties", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "GetProperties", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "GetProperties", "`fileName` cannot be an empty string.")
+ }
+
+ req, err := client.GetPropertiesPreparer(ctx, accountName, shareName, path, fileName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "GetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetPropertiesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "GetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "GetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPropertiesPreparer prepares the GetProperties request.
+func (client Client) GetPropertiesPreparer(ctx context.Context, accountName, shareName, path, fileName string) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsHead(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetPropertiesSender sends the GetProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetPropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetPropertiesResponder handles the response to the GetProperties request. The method always
+// closes the http.Response Body.
+func (client Client) GetPropertiesResponder(resp *http.Response) (result GetResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.CacheControl = resp.Header.Get("Cache-Control")
+ result.ContentDisposition = resp.Header.Get("Content-Disposition")
+ result.ContentEncoding = resp.Header.Get("Content-Encoding")
+ result.ContentLanguage = resp.Header.Get("Content-Language")
+ result.ContentMD5 = resp.Header.Get("x-ms-content-md5")
+ result.ContentType = resp.Header.Get("Content-Type")
+ result.CopyID = resp.Header.Get("x-ms-copy-id")
+ result.CopyProgress = resp.Header.Get("x-ms-copy-progress")
+ result.CopySource = resp.Header.Get("x-ms-copy-source")
+ result.CopyStatus = resp.Header.Get("x-ms-copy-status")
+ result.CopyStatusDescription = resp.Header.Get("x-ms-copy-status-description")
+ result.CopyCompletionTime = resp.Header.Get("x-ms-copy-completion-time")
+ result.Encrypted = strings.EqualFold(resp.Header.Get("x-ms-server-encrypted"), "true")
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+
+ contentLengthRaw := resp.Header.Get("Content-Length")
+ if contentLengthRaw != "" {
+ contentLength, err := strconv.Atoi(contentLengthRaw)
+ if err != nil {
+ return result, fmt.Errorf("Error parsing %q for Content-Length as an integer: %s", contentLengthRaw, err)
+ }
+ contentLengthI64 := int64(contentLength)
+ result.ContentLength = &contentLengthI64
+ }
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/files/properties_set.go b/storage/2018-11-09/file/files/properties_set.go
new file mode 100644
index 0000000..79fffc2
--- /dev/null
+++ b/storage/2018-11-09/file/files/properties_set.go
@@ -0,0 +1,160 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type SetPropertiesInput struct {
+ // Resizes a file to the specified size.
+ // If the specified byte value is less than the current size of the file,
+ // then all ranges above the specified byte value are cleared.
+ ContentLength *int64
+
+ // Modifies the cache control string for the file.
+ // If this property is not specified on the request, then the property will be cleared for the file.
+ // Subsequent calls to Get File Properties will not return this property,
+ // unless it is explicitly set on the file again.
+ ContentControl *string
+
+ // Sets the file’s Content-Disposition header.
+ // If this property is not specified on the request, then the property will be cleared for the file.
+ // Subsequent calls to Get File Properties will not return this property,
+ // unless it is explicitly set on the file again.
+ ContentDisposition *string
+
+ // Sets the file's content encoding.
+ // If this property is not specified on the request, then the property will be cleared for the file.
+ // Subsequent calls to Get File Properties will not return this property,
+ // unless it is explicitly set on the file again.
+ ContentEncoding *string
+
+ // Sets the file's content language.
+ // If this property is not specified on the request, then the property will be cleared for the file.
+ // Subsequent calls to Get File Properties will not return this property,
+ // unless it is explicitly set on the file again.
+ ContentLanguage *string
+
+ // Sets the file's MD5 hash.
+ // If this property is not specified on the request, then the property will be cleared for the file.
+ // Subsequent calls to Get File Properties will not return this property,
+ // unless it is explicitly set on the file again.
+ ContentMD5 *string
+
+ // Sets the file's content type.
+ // If this property is not specified on the request, then the property will be cleared for the file.
+ // Subsequent calls to Get File Properties will not return this property,
+ // unless it is explicitly set on the file again.
+ ContentType *string
+}
+
+// SetProperties sets the specified properties on the specified File
+func (client Client) SetProperties(ctx context.Context, accountName, shareName, path, fileName string, input SetPropertiesInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "SetProperties", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "SetProperties", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "SetProperties", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "SetProperties", "`fileName` cannot be an empty string.")
+ }
+
+ req, err := client.SetPropertiesPreparer(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "SetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetPropertiesSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "SetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "SetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetPropertiesPreparer prepares the SetProperties request.
+func (client Client) SetPropertiesPreparer(ctx context.Context, accountName, shareName, path, fileName string, input SetPropertiesInput) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-type": "file",
+ }
+
+ if input.ContentControl != nil {
+ headers["x-ms-cache-control"] = *input.ContentControl
+ }
+ if input.ContentDisposition != nil {
+ headers["x-ms-content-disposition"] = *input.ContentDisposition
+ }
+ if input.ContentEncoding != nil {
+ headers["x-ms-content-encoding"] = *input.ContentEncoding
+ }
+ if input.ContentLanguage != nil {
+ headers["x-ms-content-language"] = *input.ContentLanguage
+ }
+ if input.ContentLength != nil {
+ headers["x-ms-content-length"] = *input.ContentLength
+ }
+ if input.ContentMD5 != nil {
+ headers["x-ms-content-md5"] = *input.ContentMD5
+ }
+ if input.ContentType != nil {
+ headers["x-ms-content-type"] = *input.ContentType
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetPropertiesSender sends the SetProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetPropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetPropertiesResponder handles the response to the SetProperties request. The method always
+// closes the http.Response Body.
+func (client Client) SetPropertiesResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/files/range_clear.go b/storage/2018-11-09/file/files/range_clear.go
new file mode 100644
index 0000000..5d8145f
--- /dev/null
+++ b/storage/2018-11-09/file/files/range_clear.go
@@ -0,0 +1,112 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type ClearByteRangeInput struct {
+ StartBytes int64
+ EndBytes int64
+}
+
+// ClearByteRange clears the specified Byte Range from within the specified File
+func (client Client) ClearByteRange(ctx context.Context, accountName, shareName, path, fileName string, input ClearByteRangeInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "ClearByteRange", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "ClearByteRange", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "ClearByteRange", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "ClearByteRange", "`fileName` cannot be an empty string.")
+ }
+ if input.StartBytes < 0 {
+ return result, validation.NewError("files.Client", "ClearByteRange", "`input.StartBytes` must be greater or equal to 0.")
+ }
+ if input.EndBytes <= 0 {
+ return result, validation.NewError("files.Client", "ClearByteRange", "`input.EndBytes` must be greater than 0.")
+ }
+
+ req, err := client.ClearByteRangePreparer(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "ClearByteRange", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ClearByteRangeSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "ClearByteRange", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ClearByteRangeResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "ClearByteRange", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ClearByteRangePreparer prepares the ClearByteRange request.
+func (client Client) ClearByteRangePreparer(ctx context.Context, accountName, shareName, path, fileName string, input ClearByteRangeInput) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "range"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-write": "clear",
+ "x-ms-range": fmt.Sprintf("bytes=%d-%d", input.StartBytes, input.EndBytes),
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ClearByteRangeSender sends the ClearByteRange request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ClearByteRangeSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ClearByteRangeResponder handles the response to the ClearByteRange request. The method always
+// closes the http.Response Body.
+func (client Client) ClearByteRangeResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/files/range_get.go b/storage/2018-11-09/file/files/range_get.go
new file mode 100644
index 0000000..733d3f5
--- /dev/null
+++ b/storage/2018-11-09/file/files/range_get.go
@@ -0,0 +1,121 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetByteRangeInput struct {
+ StartBytes int64
+ EndBytes int64
+}
+
+type GetByteRangeResult struct {
+ autorest.Response
+
+ Contents []byte
+}
+
+// GetByteRange returns the specified Byte Range from the specified File.
+func (client Client) GetByteRange(ctx context.Context, accountName, shareName, path, fileName string, input GetByteRangeInput) (result GetByteRangeResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "GetByteRange", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "GetByteRange", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "GetByteRange", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "GetByteRange", "`fileName` cannot be an empty string.")
+ }
+ if input.StartBytes < 0 {
+ return result, validation.NewError("files.Client", "GetByteRange", "`input.StartBytes` must be greater or equal to 0.")
+ }
+ if input.EndBytes <= 0 {
+ return result, validation.NewError("files.Client", "GetByteRange", "`input.EndBytes` must be greater than 0.")
+ }
+ expectedBytes := input.EndBytes - input.StartBytes
+ if expectedBytes < (4 * 1024) {
+ return result, validation.NewError("files.Client", "GetByteRange", "Requested Byte Range must be at least 4KB.")
+ }
+ if expectedBytes > (4 * 1024 * 1024) {
+ return result, validation.NewError("files.Client", "GetByteRange", "Requested Byte Range must be at most 4MB.")
+ }
+
+ req, err := client.GetByteRangePreparer(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "GetByteRange", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetByteRangeSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "GetByteRange", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetByteRangeResponder(resp, expectedBytes)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "GetByteRange", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetByteRangePreparer prepares the GetByteRange request.
+func (client Client) GetByteRangePreparer(ctx context.Context, accountName, shareName, path, fileName string, input GetByteRangeInput) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-range": fmt.Sprintf("bytes=%d-%d", input.StartBytes, input.EndBytes-1),
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetByteRangeSender sends the GetByteRange request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetByteRangeSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetByteRangeResponder handles the response to the GetByteRange request. The method always
+// closes the http.Response Body.
+func (client Client) GetByteRangeResponder(resp *http.Response, length int64) (result GetByteRangeResult, err error) {
+ result.Contents = make([]byte, length)
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusPartialContent),
+ autorest.ByUnmarshallingBytes(&result.Contents),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/files/range_get_file.go b/storage/2018-11-09/file/files/range_get_file.go
new file mode 100644
index 0000000..9e5be17
--- /dev/null
+++ b/storage/2018-11-09/file/files/range_get_file.go
@@ -0,0 +1,128 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "math"
+ "runtime"
+ "sync"
+
+ "github.com/Azure/go-autorest/autorest"
+)
+
+// GetFile is a helper method to download a file by chunking it automatically
+func (client Client) GetFile(ctx context.Context, accountName, shareName, path, fileName string, parallelism int) (result autorest.Response, outputBytes []byte, err error) {
+
+ // first look up the file and check out how many bytes it is
+ file, e := client.GetProperties(ctx, accountName, shareName, path, fileName)
+ if err != nil {
+ result = file.Response
+ err = e
+ return
+ }
+
+ if file.ContentLength == nil {
+ err = fmt.Errorf("Content-Length was nil!")
+ return
+ }
+
+ length := int64(*file.ContentLength)
+ chunkSize := int64(4 * 1024 * 1024) // 4MB
+
+ if chunkSize > length {
+ chunkSize = length
+ }
+
+ // then split that up into chunks and retrieve it retrieve it into the 'results' set
+ chunks := int(math.Ceil(float64(length) / float64(chunkSize)))
+ workerCount := parallelism * runtime.NumCPU()
+ if workerCount > chunks {
+ workerCount = chunks
+ }
+
+ var waitGroup sync.WaitGroup
+ waitGroup.Add(workerCount)
+
+ results := make([]*downloadFileChunkResult, chunks)
+ errors := make(chan error, chunkSize)
+
+ for i := 0; i < chunks; i++ {
+ go func(i int) {
+ log.Printf("[DEBUG] Downloading Chunk %d of %d", i+1, chunks)
+
+ dfci := downloadFileChunkInput{
+ thisChunk: i,
+ chunkSize: chunkSize,
+ fileSize: length,
+ }
+
+ result, err := client.downloadFileChunk(ctx, accountName, shareName, path, fileName, dfci)
+ if err != nil {
+ errors <- err
+ waitGroup.Done()
+ return
+ }
+
+ // if there's no error, we should have bytes, so this is safe
+ results[i] = result
+
+ waitGroup.Done()
+ }(i)
+ }
+ waitGroup.Wait()
+
+ // TODO: we should switch to hashicorp/multi-error here
+ if len(errors) > 0 {
+ err = fmt.Errorf("Error downloading file: %s", <-errors)
+ return
+ }
+
+ // then finally put it all together, in order and return it
+ output := make([]byte, length)
+ for _, v := range results {
+ copy(output[v.startBytes:v.endBytes], v.bytes)
+ }
+
+ outputBytes = output
+ return
+}
+
+type downloadFileChunkInput struct {
+ thisChunk int
+ chunkSize int64
+ fileSize int64
+}
+
+type downloadFileChunkResult struct {
+ startBytes int64
+ endBytes int64
+ bytes []byte
+}
+
+func (client Client) downloadFileChunk(ctx context.Context, accountName, shareName, path, fileName string, input downloadFileChunkInput) (*downloadFileChunkResult, error) {
+ startBytes := input.chunkSize * int64(input.thisChunk)
+ endBytes := startBytes + input.chunkSize
+
+ // the last chunk may exceed the size of the file
+ remaining := input.fileSize - startBytes
+ if input.chunkSize > remaining {
+ endBytes = startBytes + remaining
+ }
+
+ getInput := GetByteRangeInput{
+ StartBytes: startBytes,
+ EndBytes: endBytes,
+ }
+ result, err := client.GetByteRange(ctx, accountName, shareName, path, fileName, getInput)
+ if err != nil {
+ return nil, fmt.Errorf("Error putting bytes: %s", err)
+ }
+
+ output := downloadFileChunkResult{
+ startBytes: startBytes,
+ endBytes: endBytes,
+ bytes: result.Contents,
+ }
+ return &output, nil
+}
diff --git a/storage/2018-11-09/file/files/range_get_file_test.go b/storage/2018-11-09/file/files/range_get_file_test.go
new file mode 100644
index 0000000..7b2c569
--- /dev/null
+++ b/storage/2018-11-09/file/files/range_get_file_test.go
@@ -0,0 +1,108 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/shares"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestGetSmallFile(t *testing.T) {
+ // the purpose of this test is to verify that the small, single-chunked file gets downloaded correctly
+ testGetFile(t, "small-file.png", "image/png")
+}
+
+func TestGetLargeFile(t *testing.T) {
+ // the purpose of this test is to verify that the large, multi-chunked file gets downloaded correctly
+ testGetFile(t, "blank-large-file.dmg", "application/x-apple-diskimage")
+}
+
+func testGetFile(t *testing.T, fileName string, contentType string) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := shares.NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ input := shares.CreateInput{
+ QuotaInGB: 10,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+ defer sharesClient.Delete(ctx, accountName, shareName, false)
+
+ filesClient := NewWithEnvironment(client.Environment)
+ filesClient.Client = client.PrepareWithAuthorizer(filesClient.Client, storageAuth)
+
+ // store files outside of this directory, since they're reused
+ file, err := os.Open("../../../testdata/" + fileName)
+ if err != nil {
+ t.Fatalf("Error opening: %s", err)
+ }
+
+ info, err := file.Stat()
+ if err != nil {
+ t.Fatalf("Error 'stat'-ing: %s", err)
+ }
+
+ t.Logf("[DEBUG] Creating Top Level File..")
+ createFileInput := CreateInput{
+ ContentLength: info.Size(),
+ ContentType: &contentType,
+ }
+ if _, err := filesClient.Create(ctx, accountName, shareName, "", fileName, createFileInput); err != nil {
+ t.Fatalf("Error creating Top-Level File: %s", err)
+ }
+
+ t.Logf("[DEBUG] Uploading File..")
+ if err := filesClient.PutFile(ctx, accountName, shareName, "", fileName, file, 4); err != nil {
+ t.Fatalf("Error uploading File: %s", err)
+ }
+
+ t.Logf("[DEBUG] Downloading file..")
+ _, downloadedBytes, err := filesClient.GetFile(ctx, accountName, shareName, "", fileName, 4)
+ if err != nil {
+ t.Fatalf("Error downloading file: %s", err)
+ }
+
+ t.Logf("[DEBUG] Asserting the files are the same size..")
+ expectedBytes := make([]byte, info.Size())
+ file.Read(expectedBytes)
+ if len(expectedBytes) != len(downloadedBytes) {
+ t.Fatalf("Expected %d bytes but got %d", len(expectedBytes), len(downloadedBytes))
+ }
+
+ t.Logf("[DEBUG] Asserting the files are the same content-wise..")
+ // overkill, but it's this or shasum-ing
+ for i := int64(0); i < info.Size(); i++ {
+ if expectedBytes[i] != downloadedBytes[i] {
+ t.Fatalf("Expected byte %d to be %q but got %q", i, expectedBytes[i], downloadedBytes[i])
+ }
+ }
+
+ t.Logf("[DEBUG] Deleting Top Level File..")
+ if _, err := filesClient.Delete(ctx, accountName, shareName, "", fileName); err != nil {
+ t.Fatalf("Error deleting Top-Level File: %s", err)
+ }
+
+}
diff --git a/storage/2018-11-09/file/files/range_put.go b/storage/2018-11-09/file/files/range_put.go
new file mode 100644
index 0000000..77fe101
--- /dev/null
+++ b/storage/2018-11-09/file/files/range_put.go
@@ -0,0 +1,129 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type PutByteRangeInput struct {
+ StartBytes int64
+ EndBytes int64
+
+ // Content is the File Contents for the specified range
+ // which can be at most 4MB
+ Content []byte
+}
+
+// PutByteRange puts the specified Byte Range in the specified File.
+func (client Client) PutByteRange(ctx context.Context, accountName, shareName, path, fileName string, input PutByteRangeInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "PutByteRange", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "PutByteRange", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "PutByteRange", "`shareName` must be a lower-cased string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "PutByteRange", "`fileName` cannot be an empty string.")
+ }
+ if input.StartBytes < 0 {
+ return result, validation.NewError("files.Client", "PutByteRange", "`input.StartBytes` must be greater or equal to 0.")
+ }
+ if input.EndBytes <= 0 {
+ return result, validation.NewError("files.Client", "PutByteRange", "`input.EndBytes` must be greater than 0.")
+ }
+
+ expectedBytes := input.EndBytes - input.StartBytes
+ actualBytes := len(input.Content)
+ if expectedBytes != int64(actualBytes) {
+ return result, validation.NewError("files.Client", "PutByteRange", fmt.Sprintf("The specified byte-range (%d) didn't match the content size (%d).", expectedBytes, actualBytes))
+ }
+ if expectedBytes < (4 * 1024) {
+ return result, validation.NewError("files.Client", "PutByteRange", "Specified Byte Range must be at least 4KB.")
+ }
+
+ if expectedBytes > (4 * 1024 * 1024) {
+ return result, validation.NewError("files.Client", "PutByteRange", "Specified Byte Range must be at most 4MB.")
+ }
+
+ req, err := client.PutByteRangePreparer(ctx, accountName, shareName, path, fileName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "PutByteRange", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutByteRangeSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "PutByteRange", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutByteRangeResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "PutByteRange", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutByteRangePreparer prepares the PutByteRange request.
+func (client Client) PutByteRangePreparer(ctx context.Context, accountName, shareName, path, fileName string, input PutByteRangeInput) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "range"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-write": "update",
+ "x-ms-range": fmt.Sprintf("bytes=%d-%d", input.StartBytes, input.EndBytes-1),
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithBytes(&input.Content))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutByteRangeSender sends the PutByteRange request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutByteRangeSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutByteRangeResponder handles the response to the PutByteRange request. The method always
+// closes the http.Response Body.
+func (client Client) PutByteRangeResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/files/range_put_file.go b/storage/2018-11-09/file/files/range_put_file.go
new file mode 100644
index 0000000..a39cd37
--- /dev/null
+++ b/storage/2018-11-09/file/files/range_put_file.go
@@ -0,0 +1,107 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "log"
+ "math"
+ "os"
+ "runtime"
+ "sync"
+
+ "github.com/Azure/go-autorest/autorest"
+)
+
+// PutFile is a helper method which takes a file, and automatically chunks it up, rather than having to do this yourself
+func (client Client) PutFile(ctx context.Context, accountName, shareName, path, fileName string, file *os.File, parallelism int) error {
+ fileInfo, err := file.Stat()
+ if err != nil {
+ return fmt.Errorf("Error loading file info: %s", err)
+ }
+
+ fileSize := fileInfo.Size()
+ chunkSize := 4 * 1024 * 1024 // 4MB
+ if chunkSize > int(fileSize) {
+ chunkSize = int(fileSize)
+ }
+ chunks := int(math.Ceil(float64(fileSize) / float64(chunkSize*1.0)))
+
+ workerCount := parallelism * runtime.NumCPU()
+ if workerCount > chunks {
+ workerCount = chunks
+ }
+
+ var waitGroup sync.WaitGroup
+ waitGroup.Add(workerCount)
+ errors := make(chan error, chunkSize)
+
+ for i := 0; i < chunks; i++ {
+ go func(i int) {
+ log.Printf("[DEBUG] Chunk %d of %d", i+1, chunks)
+
+ uci := uploadChunkInput{
+ thisChunk: i,
+ chunkSize: chunkSize,
+ fileSize: fileSize,
+ }
+
+ _, err := client.uploadChunk(ctx, accountName, shareName, path, fileName, uci, file)
+ if err != nil {
+ errors <- err
+ waitGroup.Done()
+ return
+ }
+
+ waitGroup.Done()
+ return
+ }(i)
+ }
+ waitGroup.Wait()
+
+ // TODO: we should switch to hashicorp/multi-error here
+ if len(errors) > 0 {
+ return fmt.Errorf("Error uploading file: %s", <-errors)
+ }
+
+ return nil
+}
+
+type uploadChunkInput struct {
+ thisChunk int
+ chunkSize int
+ fileSize int64
+}
+
+func (client Client) uploadChunk(ctx context.Context, accountName, shareName, path, fileName string, input uploadChunkInput, file *os.File) (result autorest.Response, err error) {
+ startBytes := int64(input.chunkSize * input.thisChunk)
+ endBytes := startBytes + int64(input.chunkSize)
+
+ // the last size may exceed the size of the file
+ remaining := input.fileSize - startBytes
+ if int64(input.chunkSize) > remaining {
+ endBytes = startBytes + remaining
+ }
+
+ bytesToRead := int(endBytes) - int(startBytes)
+ bytes := make([]byte, bytesToRead)
+
+ _, err = file.ReadAt(bytes, startBytes)
+ if err != nil {
+ if err != io.EOF {
+ return result, fmt.Errorf("Error reading bytes: %s", err)
+ }
+ }
+
+ putBytesInput := PutByteRangeInput{
+ StartBytes: startBytes,
+ EndBytes: endBytes,
+ Content: bytes,
+ }
+ result, err = client.PutByteRange(ctx, accountName, shareName, path, fileName, putBytesInput)
+ if err != nil {
+ return result, fmt.Errorf("Error putting bytes: %s", err)
+ }
+
+ return
+}
diff --git a/storage/2018-11-09/file/files/range_put_file_test.go b/storage/2018-11-09/file/files/range_put_file_test.go
new file mode 100644
index 0000000..841df78
--- /dev/null
+++ b/storage/2018-11-09/file/files/range_put_file_test.go
@@ -0,0 +1,86 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/shares"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestPutSmallFile(t *testing.T) {
+ // the purpose of this test is to ensure that a small file (< 4MB) is a single chunk
+ testPutFile(t, "small-file.png", "image/png")
+}
+
+func TestPutLargeFile(t *testing.T) {
+ // the purpose of this test is to ensure that large files (> 4MB) are chunked
+ testPutFile(t, "blank-large-file.dmg", "application/x-apple-diskimage")
+}
+
+func testPutFile(t *testing.T, fileName string, contentType string) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := shares.NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ input := shares.CreateInput{
+ QuotaInGB: 10,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+ defer sharesClient.Delete(ctx, accountName, shareName, false)
+
+ filesClient := NewWithEnvironment(client.Environment)
+ filesClient.Client = client.PrepareWithAuthorizer(filesClient.Client, storageAuth)
+
+ // store files outside of this directory, since they're reused
+ file, err := os.Open("../../../testdata/" + fileName)
+ if err != nil {
+ t.Fatalf("Error opening: %s", err)
+ }
+
+ info, err := file.Stat()
+ if err != nil {
+ t.Fatalf("Error 'stat'-ing: %s", err)
+ }
+
+ t.Logf("[DEBUG] Creating Top Level File..")
+ createFileInput := CreateInput{
+ ContentLength: info.Size(),
+ ContentType: &contentType,
+ }
+ if _, err := filesClient.Create(ctx, accountName, shareName, "", fileName, createFileInput); err != nil {
+ t.Fatalf("Error creating Top-Level File: %s", err)
+ }
+
+ t.Logf("[DEBUG] Uploading File..")
+ if err := filesClient.PutFile(ctx, accountName, shareName, "", fileName, file, 4); err != nil {
+ t.Fatalf("Error uploading File: %s", err)
+ }
+
+ t.Logf("[DEBUG] Deleting Top Level File..")
+ if _, err := filesClient.Delete(ctx, accountName, shareName, "", fileName); err != nil {
+ t.Fatalf("Error deleting Top-Level File: %s", err)
+ }
+}
diff --git a/storage/2018-11-09/file/files/ranges_list.go b/storage/2018-11-09/file/files/ranges_list.go
new file mode 100644
index 0000000..ea309f9
--- /dev/null
+++ b/storage/2018-11-09/file/files/ranges_list.go
@@ -0,0 +1,114 @@
+package files
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type ListRangesResult struct {
+ autorest.Response
+
+ Ranges []Range `xml:"Range"`
+}
+
+type Range struct {
+ Start string `xml:"Start"`
+ End string `xml:"End"`
+}
+
+// ListRanges returns the list of valid ranges for the specified File.
+func (client Client) ListRanges(ctx context.Context, accountName, shareName, path, fileName string) (result ListRangesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("files.Client", "ListRanges", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("files.Client", "ListRanges", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("files.Client", "ListRanges", "`shareName` must be a lower-cased string.")
+ }
+ if path == "" {
+ return result, validation.NewError("files.Client", "ListRanges", "`path` cannot be an empty string.")
+ }
+ if fileName == "" {
+ return result, validation.NewError("files.Client", "ListRanges", "`fileName` cannot be an empty string.")
+ }
+
+ req, err := client.ListRangesPreparer(ctx, accountName, shareName, path, fileName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "ListRanges", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ListRangesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "files.Client", "ListRanges", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ListRangesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "files.Client", "ListRanges", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ListRangesPreparer prepares the ListRanges request.
+func (client Client) ListRangesPreparer(ctx context.Context, accountName, shareName, path, fileName string) (*http.Request, error) {
+ if path != "" {
+ path = fmt.Sprintf("%s/", path)
+ }
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ "directory": autorest.Encode("path", path),
+ "fileName": autorest.Encode("path", fileName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "rangelist"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}/{directory}{fileName}", pathParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithQueryParameters(queryParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ListRangesSender sends the ListRanges request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ListRangesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ListRangesResponder handles the response to the ListRanges request. The method always
+// closes the http.Response Body.
+func (client Client) ListRangesResponder(resp *http.Response) (result ListRangesResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/files/resource_id.go b/storage/2018-11-09/file/files/resource_id.go
new file mode 100644
index 0000000..ed1208d
--- /dev/null
+++ b/storage/2018-11-09/file/files/resource_id.go
@@ -0,0 +1,64 @@
+package files
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given File
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, shareName, directoryName, filePath string) string {
+ domain := endpoints.GetFileEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s/%s/%s", domain, shareName, directoryName, filePath)
+}
+
+type ResourceID struct {
+ AccountName string
+ DirectoryName string
+ FileName string
+ ShareName string
+}
+
+// ParseResourceID parses the specified Resource ID and returns an object
+// which can be used to interact with Files within a Storage Share.
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://account1.file.core.chinacloudapi.cn/share1/directory1/file1.txt
+ // example: https://account1.file.core.chinacloudapi.cn/share1/directory1/directory2/file1.txt
+
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ path := strings.TrimPrefix(uri.Path, "/")
+ segments := strings.Split(path, "/")
+ if len(segments) == 0 {
+ return nil, fmt.Errorf("Expected the path to contain segments but got none")
+ }
+
+ shareName := segments[0]
+ fileName := segments[len(segments)-1]
+
+ directoryName := strings.TrimPrefix(path, shareName)
+ directoryName = strings.TrimPrefix(directoryName, "/")
+ directoryName = strings.TrimSuffix(directoryName, fileName)
+ directoryName = strings.TrimSuffix(directoryName, "/")
+ return &ResourceID{
+ AccountName: *accountName,
+ ShareName: shareName,
+ DirectoryName: directoryName,
+ FileName: fileName,
+ }, nil
+}
diff --git a/storage/2018-11-09/file/files/resource_id_test.go b/storage/2018-11-09/file/files/resource_id_test.go
new file mode 100644
index 0000000..1b521ac
--- /dev/null
+++ b/storage/2018-11-09/file/files/resource_id_test.go
@@ -0,0 +1,131 @@
+package files
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.file.core.chinacloudapi.cn/share1/directory1/file1.txt",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.file.core.cloudapi.de/share1/directory1/file1.txt",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.file.core.windows.net/share1/directory1/file1.txt",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.file.core.usgovcloudapi.net/share1/directory1/file1.txt",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "share1", "directory1", "file1.txt")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.file.core.chinacloudapi.cn/share1/directory1/file1.txt",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.file.core.cloudapi.de/share1/directory1/file1.txt",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.file.core.windows.net/share1/directory1/file1.txt",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.file.core.usgovcloudapi.net/share1/directory1/file1.txt",
+ },
+ }
+
+ t.Logf("[DEBUG] Top Level Files")
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.ShareName != "share1" {
+ t.Fatalf("Expected Share Name to be `share1` but got %q", actual.ShareName)
+ }
+ if actual.DirectoryName != "directory1" {
+ t.Fatalf("Expected Directory Name to be `directory1` but got %q", actual.DirectoryName)
+ }
+ if actual.FileName != "file1.txt" {
+ t.Fatalf("Expected File Name to be `file1.txt` but got %q", actual.FileName)
+ }
+ }
+
+ testData = []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.file.core.chinacloudapi.cn/share1/directory1/directory2/file1.txt",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.file.core.cloudapi.de/share1/directory1/directory2/file1.txt",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.file.core.windows.net/share1/directory1/directory2/file1.txt",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.file.core.usgovcloudapi.net/share1/directory1/directory2/file1.txt",
+ },
+ }
+
+ t.Logf("[DEBUG] Nested Files")
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.ShareName != "share1" {
+ t.Fatalf("Expected Share Name to be `share1` but got %q", actual.ShareName)
+ }
+ if actual.DirectoryName != "directory1/directory2" {
+ t.Fatalf("Expected Directory Name to be `directory1/directory2` but got %q", actual.DirectoryName)
+ }
+ if actual.FileName != "file1.txt" {
+ t.Fatalf("Expected File Name to be `file1.txt` but got %q", actual.FileName)
+ }
+ }
+}
diff --git a/storage/2018-11-09/file/files/version.go b/storage/2018-11-09/file/files/version.go
new file mode 100644
index 0000000..8d135a3
--- /dev/null
+++ b/storage/2018-11-09/file/files/version.go
@@ -0,0 +1,14 @@
+package files
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2018-11-09"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2018-11-09/file/shares/README.md b/storage/2018-11-09/file/shares/README.md
new file mode 100644
index 0000000..94b3bdb
--- /dev/null
+++ b/storage/2018-11-09/file/shares/README.md
@@ -0,0 +1,43 @@
+## File Storage Shares SDK for API version 2018-11-09
+
+This package allows you to interact with the Shares File Storage API
+
+### Supported Authorizers
+
+* Azure Active Directory (for the Resource Endpoint `https://storage.azure.com`)
+* SharedKeyLite (Blob, File & Queue)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/file/shares"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ shareName := "myshare"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ sharesClient := shares.New()
+ sharesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ input := shares.CreateInput{
+ QuotaInGB: 2,
+ }
+ if _, err := sharesClient.Create(ctx, accountName, shareName, input); err != nil {
+ return fmt.Errorf("Error creating Share: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2018-11-09/file/shares/acl_get.go b/storage/2018-11-09/file/shares/acl_get.go
new file mode 100644
index 0000000..ea6ff4c
--- /dev/null
+++ b/storage/2018-11-09/file/shares/acl_get.go
@@ -0,0 +1,98 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetACLResult struct {
+ autorest.Response
+
+ SignedIdentifiers []SignedIdentifier `xml:"SignedIdentifier"`
+}
+
+// GetACL get the Access Control List for the specified Storage Share
+func (client Client) GetACL(ctx context.Context, accountName, shareName string) (result GetACLResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "GetACL", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "GetACL", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "GetACL", "`shareName` must be a lower-cased string.")
+ }
+
+ req, err := client.GetACLPreparer(ctx, accountName, shareName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetACL", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetACLSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetACL", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetACLResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetACL", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetACLPreparer prepares the GetACL request.
+func (client Client) GetACLPreparer(ctx context.Context, accountName, shareName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "comp": autorest.Encode("query", "acl"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetACLSender sends the GetACL request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetACLSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetACLResponder handles the response to the GetACL request. The method always
+// closes the http.Response Body.
+func (client Client) GetACLResponder(resp *http.Response) (result GetACLResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/shares/acl_set.go b/storage/2018-11-09/file/shares/acl_set.go
new file mode 100644
index 0000000..18d1788
--- /dev/null
+++ b/storage/2018-11-09/file/shares/acl_set.go
@@ -0,0 +1,103 @@
+package shares
+
+import (
+ "context"
+ "encoding/xml"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type setAcl struct {
+ SignedIdentifiers []SignedIdentifier `xml:"SignedIdentifier"`
+
+ XMLName xml.Name `xml:"SignedIdentifiers"`
+}
+
+// SetACL sets the specified Access Control List on the specified Storage Share
+func (client Client) SetACL(ctx context.Context, accountName, shareName string, acls []SignedIdentifier) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "SetACL", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "SetACL", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "SetACL", "`shareName` must be a lower-cased string.")
+ }
+
+ req, err := client.SetACLPreparer(ctx, accountName, shareName, acls)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetACL", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetACLSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetACL", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetACLResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetACL", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetACLPreparer prepares the SetACL request.
+func (client Client) SetACLPreparer(ctx context.Context, accountName, shareName string, acls []SignedIdentifier) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "comp": autorest.Encode("query", "acl"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ input := setAcl{
+ SignedIdentifiers: acls,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithXML(&input))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetACLSender sends the SetACL request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetACLSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetACLResponder handles the response to the SetACL request. The method always
+// closes the http.Response Body.
+func (client Client) SetACLResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/shares/client.go b/storage/2018-11-09/file/shares/client.go
new file mode 100644
index 0000000..4f3a6f9
--- /dev/null
+++ b/storage/2018-11-09/file/shares/client.go
@@ -0,0 +1,25 @@
+package shares
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for File Storage Shares.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2018-11-09/file/shares/create.go b/storage/2018-11-09/file/shares/create.go
new file mode 100644
index 0000000..84fd40d
--- /dev/null
+++ b/storage/2018-11-09/file/shares/create.go
@@ -0,0 +1,109 @@
+package shares
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type CreateInput struct {
+ // Specifies the maximum size of the share, in gigabytes.
+ // Must be greater than 0, and less than or equal to 5TB (5120).
+ QuotaInGB int
+
+ MetaData map[string]string
+}
+
+// Create creates the specified Storage Share within the specified Storage Account
+func (client Client) Create(ctx context.Context, accountName, shareName string, input CreateInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "Create", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "Create", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "Create", "`shareName` must be a lower-cased string.")
+ }
+ if input.QuotaInGB <= 0 || input.QuotaInGB > 5120 {
+ return result, validation.NewError("shares.Client", "Create", "`input.QuotaInGB` must be greater than 0, and less than/equal to 5TB (5120 GB)")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("shares.Client", "Create", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.CreatePreparer(ctx, accountName, shareName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "Create", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "Create", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "Create", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreatePreparer prepares the Create request.
+func (client Client) CreatePreparer(ctx context.Context, accountName, shareName string, input CreateInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "share"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-share-quota": input.QuotaInGB,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSender sends the Create request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateResponder handles the response to the Create request. The method always
+// closes the http.Response Body.
+func (client Client) CreateResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/shares/delete.go b/storage/2018-11-09/file/shares/delete.go
new file mode 100644
index 0000000..70ef985
--- /dev/null
+++ b/storage/2018-11-09/file/shares/delete.go
@@ -0,0 +1,94 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete deletes the specified Storage Share from within a Storage Account
+func (client Client) Delete(ctx context.Context, accountName, shareName string, deleteSnapshots bool) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "Delete", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "Delete", "`shareName` must be a lower-cased string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, shareName, deleteSnapshots)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, shareName string, deleteSnapshots bool) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "share"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ if deleteSnapshots {
+ headers["x-ms-delete-snapshots"] = "include"
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/shares/lifecycle_test.go b/storage/2018-11-09/file/shares/lifecycle_test.go
new file mode 100644
index 0000000..fbab96d
--- /dev/null
+++ b/storage/2018-11-09/file/shares/lifecycle_test.go
@@ -0,0 +1,152 @@
+package shares
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestSharesLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ shareName := fmt.Sprintf("share-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ sharesClient := NewWithEnvironment(client.Environment)
+ sharesClient.Client = client.PrepareWithAuthorizer(sharesClient.Client, storageAuth)
+
+ input := CreateInput{
+ QuotaInGB: 1,
+ }
+ _, err = sharesClient.Create(ctx, accountName, shareName, input)
+ if err != nil {
+ t.Fatalf("Error creating fileshare: %s", err)
+ }
+
+ snapshot, err := sharesClient.CreateSnapshot(ctx, accountName, shareName, CreateSnapshotInput{})
+ if err != nil {
+ t.Fatalf("Error taking snapshot: %s", err)
+ }
+ t.Logf("Snapshot Date Time: %s", snapshot.SnapshotDateTime)
+
+ snapshotDetails, err := sharesClient.GetSnapshot(ctx, accountName, shareName, snapshot.SnapshotDateTime)
+ if err != nil {
+ t.Fatalf("Error retrieving snapshot: %s", err)
+ }
+
+ t.Logf("MetaData: %s", snapshotDetails.MetaData)
+
+ _, err = sharesClient.DeleteSnapshot(ctx, accountName, shareName, snapshot.SnapshotDateTime)
+ if err != nil {
+ t.Fatalf("Error deleting snapshot: %s", err)
+ }
+
+ stats, err := sharesClient.GetStats(ctx, accountName, shareName)
+ if err != nil {
+ t.Fatalf("Error retrieving stats: %s", err)
+ }
+
+ if stats.ShareUsageBytes != 0 {
+ t.Fatalf("Expected `stats.ShareUsageBytes` to be 0 but got: %d", stats.ShareUsageBytes)
+ }
+
+ share, err := sharesClient.GetProperties(ctx, accountName, shareName)
+ if err != nil {
+ t.Fatalf("Error retrieving share: %s", err)
+ }
+ if share.ShareQuota != 1 {
+ t.Fatalf("Expected Quota to be 1 but got: %d", share.ShareQuota)
+ }
+
+ _, err = sharesClient.SetProperties(ctx, accountName, shareName, 5)
+ if err != nil {
+ t.Fatalf("Error updating quota: %s", err)
+ }
+
+ share, err = sharesClient.GetProperties(ctx, accountName, shareName)
+ if err != nil {
+ t.Fatalf("Error retrieving share: %s", err)
+ }
+ if share.ShareQuota != 5 {
+ t.Fatalf("Expected Quota to be 5 but got: %d", share.ShareQuota)
+ }
+
+ updatedMetaData := map[string]string{
+ "hello": "world",
+ }
+ _, err = sharesClient.SetMetaData(ctx, accountName, shareName, updatedMetaData)
+ if err != nil {
+ t.Fatalf("Erorr setting metadata: %s", err)
+ }
+
+ result, err := sharesClient.GetMetaData(ctx, accountName, shareName)
+ if err != nil {
+ t.Fatalf("Error retrieving metadata: %s", err)
+ }
+
+ if result.MetaData["hello"] != "world" {
+ t.Fatalf("Expected metadata `hello` to be `world` but got: %q", result.MetaData["hello"])
+ }
+ if len(result.MetaData) != 1 {
+ t.Fatalf("Expected metadata to be 1 item but got: %s", result.MetaData)
+ }
+
+ acls, err := sharesClient.GetACL(ctx, accountName, shareName)
+ if err != nil {
+ t.Fatalf("Error retrieving ACL's: %s", err)
+ }
+ if len(acls.SignedIdentifiers) != 0 {
+ t.Fatalf("Expected 0 identifiers but got %d", len(acls.SignedIdentifiers))
+ }
+
+ updatedAcls := []SignedIdentifier{
+ {
+ Id: "abc123",
+ AccessPolicy: AccessPolicy{
+ Start: "2020-07-01T08:49:37.0000000Z",
+ Expiry: "2020-07-01T09:49:37.0000000Z",
+ Permission: "rwd",
+ },
+ },
+ {
+ Id: "bcd234",
+ AccessPolicy: AccessPolicy{
+ Start: "2020-07-01T08:49:37.0000000Z",
+ Expiry: "2020-07-01T09:49:37.0000000Z",
+ Permission: "rwd",
+ },
+ },
+ }
+ _, err = sharesClient.SetACL(ctx, accountName, shareName, updatedAcls)
+ if err != nil {
+ t.Fatalf("Error setting ACL's: %s", err)
+ }
+
+ acls, err = sharesClient.GetACL(ctx, accountName, shareName)
+ if err != nil {
+ t.Fatalf("Error retrieving ACL's: %s", err)
+ }
+ if len(acls.SignedIdentifiers) != 2 {
+ t.Fatalf("Expected 2 identifiers but got %d", len(acls.SignedIdentifiers))
+ }
+
+ _, err = sharesClient.Delete(ctx, accountName, shareName, false)
+ if err != nil {
+ t.Fatalf("Error deleting Share: %s", err)
+ }
+}
diff --git a/storage/2018-11-09/file/shares/metadata_get.go b/storage/2018-11-09/file/shares/metadata_get.go
new file mode 100644
index 0000000..9fa4d9f
--- /dev/null
+++ b/storage/2018-11-09/file/shares/metadata_get.go
@@ -0,0 +1,102 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetMetaDataResult struct {
+ autorest.Response
+
+ MetaData map[string]string
+}
+
+// GetMetaData returns the MetaData associated with the specified Storage Share
+func (client Client) GetMetaData(ctx context.Context, accountName, shareName string) (result GetMetaDataResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "GetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "GetMetaData", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "GetMetaData", "`shareName` must be a lower-cased string.")
+ }
+
+ req, err := client.GetMetaDataPreparer(ctx, accountName, shareName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetMetaDataSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetMetaDataPreparer prepares the GetMetaData request.
+func (client Client) GetMetaDataPreparer(ctx context.Context, accountName, shareName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetMetaDataSender sends the GetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetMetaDataResponder handles the response to the GetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) GetMetaDataResponder(resp *http.Response) (result GetMetaDataResult, err error) {
+ if resp.Header != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/shares/metadata_set.go b/storage/2018-11-09/file/shares/metadata_set.go
new file mode 100644
index 0000000..7e64e60
--- /dev/null
+++ b/storage/2018-11-09/file/shares/metadata_set.go
@@ -0,0 +1,97 @@
+package shares
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// SetMetaData sets the MetaData on the specified Storage Share
+func (client Client) SetMetaData(ctx context.Context, accountName, shareName string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "SetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "SetMetaData", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "SetMetaData", "`shareName` must be a lower-cased string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("shares.Client", "SetMetaData", fmt.Sprintf("`metadata` is not valid: %s.", err))
+ }
+
+ req, err := client.SetMetaDataPreparer(ctx, accountName, shareName, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetMetaDataSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetMetaDataPreparer prepares the SetMetaData request.
+func (client Client) SetMetaDataPreparer(ctx context.Context, accountName, shareName string, metaData map[string]string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "comp": autorest.Encode("query", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetPropertiesSetMetaDataSender sends the SetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetMetaDataResponder handles the response to the SetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) SetMetaDataResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/shares/models.go b/storage/2018-11-09/file/shares/models.go
new file mode 100644
index 0000000..31ef7c2
--- /dev/null
+++ b/storage/2018-11-09/file/shares/models.go
@@ -0,0 +1,12 @@
+package shares
+
+type SignedIdentifier struct {
+ Id string `xml:"Id"`
+ AccessPolicy AccessPolicy `xml:"AccessPolicy"`
+}
+
+type AccessPolicy struct {
+ Start string `xml:"Start"`
+ Expiry string `xml:"Expiry"`
+ Permission string `xml:"Permission"`
+}
diff --git a/storage/2018-11-09/file/shares/properties_get.go b/storage/2018-11-09/file/shares/properties_get.go
new file mode 100644
index 0000000..80e26a4
--- /dev/null
+++ b/storage/2018-11-09/file/shares/properties_get.go
@@ -0,0 +1,111 @@
+package shares
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetPropertiesResult struct {
+ autorest.Response
+
+ MetaData map[string]string
+ ShareQuota int
+}
+
+// GetProperties returns the properties about the specified Storage Share
+func (client Client) GetProperties(ctx context.Context, accountName, shareName string) (result GetPropertiesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "GetProperties", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "GetProperties", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "GetProperties", "`shareName` must be a lower-cased string.")
+ }
+
+ req, err := client.GetPropertiesPreparer(ctx, accountName, shareName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetPropertiesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPropertiesPreparer prepares the GetProperties request.
+func (client Client) GetPropertiesPreparer(ctx context.Context, accountName, shareName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetPropertiesSender sends the GetProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetPropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetPropertiesResponder handles the response to the GetProperties request. The method always
+// closes the http.Response Body.
+func (client Client) GetPropertiesResponder(resp *http.Response) (result GetPropertiesResult, err error) {
+ if resp.Header != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+
+ quotaRaw := resp.Header.Get("x-ms-share-quota")
+ quota, e := strconv.Atoi(quotaRaw)
+ if e != nil {
+ return result, fmt.Errorf("Error converting %q to an integer: %s", quotaRaw, err)
+ }
+ result.ShareQuota = quota
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/shares/properties_set.go b/storage/2018-11-09/file/shares/properties_set.go
new file mode 100644
index 0000000..4553e5e
--- /dev/null
+++ b/storage/2018-11-09/file/shares/properties_set.go
@@ -0,0 +1,95 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// SetProperties lets you update the Quota for the specified Storage Share
+func (client Client) SetProperties(ctx context.Context, accountName, shareName string, newQuotaGB int) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "SetProperties", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "SetProperties", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "SetProperties", "`shareName` must be a lower-cased string.")
+ }
+ if newQuotaGB <= 0 || newQuotaGB > 5120 {
+ return result, validation.NewError("shares.Client", "SetProperties", "`newQuotaGB` must be greater than 0, and less than/equal to 5TB (5120 GB)")
+ }
+
+ req, err := client.SetPropertiesPreparer(ctx, accountName, shareName, newQuotaGB)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetPropertiesSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetPropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "SetProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetPropertiesPreparer prepares the SetProperties request.
+func (client Client) SetPropertiesPreparer(ctx context.Context, accountName, shareName string, quotaGB int) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "comp": autorest.Encode("query", "properties"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "x-ms-share-quota": quotaGB,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetPropertiesSender sends the SetProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetPropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetPropertiesResponder handles the response to the SetProperties request. The method always
+// closes the http.Response Body.
+func (client Client) SetPropertiesResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/shares/resource_id.go b/storage/2018-11-09/file/shares/resource_id.go
new file mode 100644
index 0000000..bfdcbfd
--- /dev/null
+++ b/storage/2018-11-09/file/shares/resource_id.go
@@ -0,0 +1,46 @@
+package shares
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given File Share
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, shareName string) string {
+ domain := endpoints.GetFileEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s", domain, shareName)
+}
+
+type ResourceID struct {
+ AccountName string
+ ShareName string
+}
+
+// ParseResourceID parses the specified Resource ID and returns an object
+// which can be used to interact with the Storage Shares SDK
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://foo.file.core.windows.net/Bar
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ shareName := strings.TrimPrefix(uri.Path, "/")
+ return &ResourceID{
+ AccountName: *accountName,
+ ShareName: shareName,
+ }, nil
+}
diff --git a/storage/2018-11-09/file/shares/resource_id_test.go b/storage/2018-11-09/file/shares/resource_id_test.go
new file mode 100644
index 0000000..1b7eea3
--- /dev/null
+++ b/storage/2018-11-09/file/shares/resource_id_test.go
@@ -0,0 +1,79 @@
+package shares
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.file.core.chinacloudapi.cn/share1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.file.core.cloudapi.de/share1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.file.core.windows.net/share1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.file.core.usgovcloudapi.net/share1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "share1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.file.core.chinacloudapi.cn/share1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.file.core.cloudapi.de/share1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.file.core.windows.net/share1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.file.core.usgovcloudapi.net/share1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected the account name to be `account1` but got %q", actual.AccountName)
+ }
+
+ if actual.ShareName != "share1" {
+ t.Fatalf("Expected the share name to be `share1` but got %q", actual.ShareName)
+ }
+ }
+}
diff --git a/storage/2018-11-09/file/shares/snapshot_create.go b/storage/2018-11-09/file/shares/snapshot_create.go
new file mode 100644
index 0000000..0ded38b
--- /dev/null
+++ b/storage/2018-11-09/file/shares/snapshot_create.go
@@ -0,0 +1,115 @@
+package shares
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type CreateSnapshotInput struct {
+ MetaData map[string]string
+}
+
+type CreateSnapshotResult struct {
+ autorest.Response
+
+ // This header is a DateTime value that uniquely identifies the share snapshot.
+ // The value of this header may be used in subsequent requests to access the share snapshot.
+ // This value is opaque.
+ SnapshotDateTime string
+}
+
+// CreateSnapshot creates a read-only snapshot of the share
+// A share can support creation of 200 share snapshots. Attempting to create more than 200 share snapshots fails with 409 (Conflict).
+// Attempting to create a share snapshot while a previous Snapshot Share operation is in progress fails with 409 (Conflict).
+func (client Client) CreateSnapshot(ctx context.Context, accountName, shareName string, input CreateSnapshotInput) (result CreateSnapshotResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "CreateSnapshot", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "CreateSnapshot", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "CreateSnapshot", "`shareName` must be a lower-cased string.")
+ }
+ if err := metadata.Validate(input.MetaData); err != nil {
+ return result, validation.NewError("shares.Client", "CreateSnapshot", fmt.Sprintf("`input.MetaData` is not valid: %s.", err))
+ }
+
+ req, err := client.CreateSnapshotPreparer(ctx, accountName, shareName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "CreateSnapshot", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSnapshotSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "CreateSnapshot", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateSnapshotResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "CreateSnapshot", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreateSnapshotPreparer prepares the CreateSnapshot request.
+func (client Client) CreateSnapshotPreparer(ctx context.Context, accountName, shareName string, input CreateSnapshotInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "snapshot"),
+ "restype": autorest.Encode("query", "share"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, input.MetaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSnapshotSender sends the CreateSnapshot request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSnapshotSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateSnapshotResponder handles the response to the CreateSnapshot request. The method always
+// closes the http.Response Body.
+func (client Client) CreateSnapshotResponder(resp *http.Response) (result CreateSnapshotResult, err error) {
+ result.SnapshotDateTime = resp.Header.Get("x-ms-snapshot")
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/shares/snapshot_delete.go b/storage/2018-11-09/file/shares/snapshot_delete.go
new file mode 100644
index 0000000..1f5d665
--- /dev/null
+++ b/storage/2018-11-09/file/shares/snapshot_delete.go
@@ -0,0 +1,94 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// DeleteSnapshot deletes the specified Snapshot of a Storage Share
+func (client Client) DeleteSnapshot(ctx context.Context, accountName, shareName string, shareSnapshot string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "DeleteSnapshot", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "DeleteSnapshot", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "DeleteSnapshot", "`shareName` must be a lower-cased string.")
+ }
+ if shareSnapshot == "" {
+ return result, validation.NewError("shares.Client", "DeleteSnapshot", "`shareSnapshot` cannot be an empty string.")
+ }
+
+ req, err := client.DeleteSnapshotPreparer(ctx, accountName, shareName, shareSnapshot)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "DeleteSnapshot", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSnapshotSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "DeleteSnapshot", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteSnapshotResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "DeleteSnapshot", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeleteSnapshotPreparer prepares the DeleteSnapshot request.
+func (client Client) DeleteSnapshotPreparer(ctx context.Context, accountName, shareName string, shareSnapshot string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("path", "share"),
+ "sharesnapshot": autorest.Encode("query", shareSnapshot),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSnapshotSender sends the DeleteSnapshot request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSnapshotSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteSnapshotResponder handles the response to the DeleteSnapshot request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteSnapshotResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/shares/snapshot_get.go b/storage/2018-11-09/file/shares/snapshot_get.go
new file mode 100644
index 0000000..2cf5f16
--- /dev/null
+++ b/storage/2018-11-09/file/shares/snapshot_get.go
@@ -0,0 +1,105 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetSnapshotPropertiesResult struct {
+ autorest.Response
+
+ MetaData map[string]string
+}
+
+// GetSnapshot gets information about the specified Snapshot of the specified Storage Share
+func (client Client) GetSnapshot(ctx context.Context, accountName, shareName, snapshotShare string) (result GetSnapshotPropertiesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "GetSnapshot", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "GetSnapshot", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "GetSnapshot", "`shareName` must be a lower-cased string.")
+ }
+ if snapshotShare == "" {
+ return result, validation.NewError("shares.Client", "GetSnapshot", "`snapshotShare` cannot be an empty string.")
+ }
+
+ req, err := client.GetSnapshotPreparer(ctx, accountName, shareName, snapshotShare)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetSnapshot", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetSnapshotSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetSnapshot", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetSnapshotResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetSnapshot", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetSnapshotPreparer prepares the GetSnapshot request.
+func (client Client) GetSnapshotPreparer(ctx context.Context, accountName, shareName, snapshotShare string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "snapshot": autorest.Encode("query", snapshotShare),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetSnapshotSender sends the GetSnapshot request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetSnapshotSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetSnapshotResponder handles the response to the GetSnapshot request. The method always
+// closes the http.Response Body.
+func (client Client) GetSnapshotResponder(resp *http.Response) (result GetSnapshotPropertiesResult, err error) {
+ if resp.Header != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/shares/stats.go b/storage/2018-11-09/file/shares/stats.go
new file mode 100644
index 0000000..3539ecc
--- /dev/null
+++ b/storage/2018-11-09/file/shares/stats.go
@@ -0,0 +1,100 @@
+package shares
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetStatsResult struct {
+ autorest.Response
+
+ // The approximate size of the data stored on the share.
+ // Note that this value may not include all recently created or recently resized files.
+ ShareUsageBytes int64 `xml:"ShareUsageBytes"`
+}
+
+// GetStats returns information about the specified Storage Share
+func (client Client) GetStats(ctx context.Context, accountName, shareName string) (result GetStatsResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("shares.Client", "GetStats", "`accountName` cannot be an empty string.")
+ }
+ if shareName == "" {
+ return result, validation.NewError("shares.Client", "GetStats", "`shareName` cannot be an empty string.")
+ }
+ if strings.ToLower(shareName) != shareName {
+ return result, validation.NewError("shares.Client", "GetStats", "`shareName` must be a lower-cased string.")
+ }
+
+ req, err := client.GetStatsPreparer(ctx, accountName, shareName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetStats", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetStatsSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetStats", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetStatsResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "shares.Client", "GetStats", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetStatsPreparer prepares the GetStats request.
+func (client Client) GetStatsPreparer(ctx context.Context, accountName, shareName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "shareName": autorest.Encode("path", shareName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "restype": autorest.Encode("query", "share"),
+ "comp": autorest.Encode("query", "stats"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetFileEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{shareName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetStatsSender sends the GetStats request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetStatsSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetStatsResponder handles the response to the GetStats request. The method always
+// closes the http.Response Body.
+func (client Client) GetStatsResponder(resp *http.Response) (result GetStatsResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/file/shares/version.go b/storage/2018-11-09/file/shares/version.go
new file mode 100644
index 0000000..9249149
--- /dev/null
+++ b/storage/2018-11-09/file/shares/version.go
@@ -0,0 +1,14 @@
+package shares
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2018-11-09"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2018-11-09/queue/messages/README.md b/storage/2018-11-09/queue/messages/README.md
new file mode 100644
index 0000000..3c06965
--- /dev/null
+++ b/storage/2018-11-09/queue/messages/README.md
@@ -0,0 +1,43 @@
+## Queue Storage Messages SDK for API version 2018-11-09
+
+This package allows you to interact with the Messages Queue Storage API
+
+### Supported Authorizers
+
+* Azure Active Directory (for the Resource Endpoint `https://storage.azure.com`)
+* SharedKeyLite (Blob, File & Queue)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/queue/messages"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ queueName := "myqueue"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ messagesClient := messages.New()
+ messagesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ input := messages.PutInput{
+ Message: "hello",
+ }
+ if _, err := messagesClient.Put(ctx, accountName, queueName, input); err != nil {
+ return fmt.Errorf("Error creating Message: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2018-11-09/queue/messages/client.go b/storage/2018-11-09/queue/messages/client.go
new file mode 100644
index 0000000..08b1801
--- /dev/null
+++ b/storage/2018-11-09/queue/messages/client.go
@@ -0,0 +1,25 @@
+package messages
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for Messages.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2018-11-09/queue/messages/delete.go b/storage/2018-11-09/queue/messages/delete.go
new file mode 100644
index 0000000..1ec0e1a
--- /dev/null
+++ b/storage/2018-11-09/queue/messages/delete.go
@@ -0,0 +1,97 @@
+package messages
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete deletes a specific message
+func (client Client) Delete(ctx context.Context, accountName, queueName, messageID, popReceipt string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("messages.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("messages.Client", "Delete", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("messages.Client", "Delete", "`queueName` must be a lower-cased string.")
+ }
+ if messageID == "" {
+ return result, validation.NewError("messages.Client", "Delete", "`messageID` cannot be an empty string.")
+ }
+ if popReceipt == "" {
+ return result, validation.NewError("messages.Client", "Delete", "`popReceipt` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, queueName, messageID, popReceipt)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "messages.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, queueName, messageID, popReceipt string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ "messageID": autorest.Encode("path", messageID),
+ }
+
+ queryParameters := map[string]interface{}{
+ "popreceipt": autorest.Encode("query", popReceipt),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}/messages/{messageID}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/queue/messages/get.go b/storage/2018-11-09/queue/messages/get.go
new file mode 100644
index 0000000..4edeb6d
--- /dev/null
+++ b/storage/2018-11-09/queue/messages/get.go
@@ -0,0 +1,112 @@
+package messages
+
+import (
+ "context"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetInput struct {
+ // VisibilityTimeout specifies the new visibility timeout value, in seconds, relative to server time.
+ // The new value must be larger than or equal to 0, and cannot be larger than 7 days.
+ VisibilityTimeout *int
+}
+
+// Get retrieves one or more messages from the front of the queue
+func (client Client) Get(ctx context.Context, accountName, queueName string, numberOfMessages int, input GetInput) (result QueueMessagesListResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("messages.Client", "Get", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("messages.Client", "Get", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("messages.Client", "Get", "`queueName` must be a lower-cased string.")
+ }
+ if numberOfMessages < 1 || numberOfMessages > 32 {
+ return result, validation.NewError("messages.Client", "Get", "`numberOfMessages` must be between 1 and 32.")
+ }
+ if input.VisibilityTimeout != nil {
+ t := *input.VisibilityTimeout
+ maxTime := (time.Hour * 24 * 7).Seconds()
+ if t < 1 || t < int(maxTime) {
+ return result, validation.NewError("messages.Client", "Get", "`input.VisibilityTimeout` must be larger than or equal to 1 second, and cannot be larger than 7 days.")
+ }
+ }
+
+ req, err := client.GetPreparer(ctx, accountName, queueName, numberOfMessages, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Get", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "messages.Client", "Get", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Get", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPreparer prepares the Get request.
+func (client Client) GetPreparer(ctx context.Context, accountName, queueName string, numberOfMessages int, input GetInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "numofmessages": autorest.Encode("query", numberOfMessages),
+ }
+
+ if input.VisibilityTimeout != nil {
+ queryParameters["visibilitytimeout"] = autorest.Encode("query", *input.VisibilityTimeout)
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}/messages", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetSender sends the Get request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetResponder handles the response to the Get request. The method always
+// closes the http.Response Body.
+func (client Client) GetResponder(resp *http.Response) (result QueueMessagesListResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ autorest.ByUnmarshallingXML(&result),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/queue/messages/lifecycle_test.go b/storage/2018-11-09/queue/messages/lifecycle_test.go
new file mode 100644
index 0000000..95d8da5
--- /dev/null
+++ b/storage/2018-11-09/queue/messages/lifecycle_test.go
@@ -0,0 +1,95 @@
+package messages
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/queue/queues"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestLifeCycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ queueName := fmt.Sprintf("queue-%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ queuesClient := queues.NewWithEnvironment(client.Environment)
+ queuesClient.Client = client.PrepareWithStorageResourceManagerAuth(queuesClient.Client)
+
+ storageAuth := auth.NewSharedKeyLiteAuthorizer(accountName, testData.StorageAccountKey)
+ messagesClient := NewWithEnvironment(client.Environment)
+ messagesClient.Client = client.PrepareWithAuthorizer(messagesClient.Client, storageAuth)
+
+ _, err = queuesClient.Create(ctx, accountName, queueName, map[string]string{})
+ if err != nil {
+ t.Fatalf("Error creating queue: %s", err)
+ }
+ defer queuesClient.Delete(ctx, accountName, queueName)
+
+ input := PutInput{
+ Message: "ohhai",
+ }
+ putResp, err := messagesClient.Put(ctx, accountName, queueName, input)
+ if err != nil {
+ t.Fatalf("Error putting message in queue: %s", err)
+ }
+
+ messageId := (*putResp.QueueMessages)[0].MessageId
+ popReceipt := (*putResp.QueueMessages)[0].PopReceipt
+
+ _, err = messagesClient.Update(ctx, accountName, queueName, messageId, UpdateInput{
+ PopReceipt: popReceipt,
+ Message: "Updated message",
+ VisibilityTimeout: 65,
+ })
+ if err != nil {
+ t.Fatalf("Error updating: %s", err)
+ }
+
+ for i := 0; i < 5; i++ {
+ input := PutInput{
+ Message: fmt.Sprintf("Message %d", i),
+ }
+ _, err := messagesClient.Put(ctx, accountName, queueName, input)
+ if err != nil {
+ t.Fatalf("Error putting message %d in queue: %s", i, err)
+ }
+ }
+
+ peakedMessages, err := messagesClient.Peek(ctx, accountName, queueName, 3)
+ if err != nil {
+ t.Fatalf("Error peaking messages: %s", err)
+ }
+
+ for _, v := range *peakedMessages.QueueMessages {
+ t.Logf("Message: %q", v.MessageId)
+ }
+
+ retrievedMessages, err := messagesClient.Get(ctx, accountName, queueName, 6, GetInput{})
+ if err != nil {
+ t.Fatalf("Error retrieving messages: %s", err)
+ }
+
+ for _, v := range *retrievedMessages.QueueMessages {
+ t.Logf("Message: %q", v.MessageId)
+
+ _, err = messagesClient.Delete(ctx, accountName, queueName, v.MessageId, v.PopReceipt)
+ if err != nil {
+ t.Fatalf("Error deleting message from queue: %s", err)
+ }
+ }
+}
diff --git a/storage/2018-11-09/queue/messages/models.go b/storage/2018-11-09/queue/messages/models.go
new file mode 100644
index 0000000..67815a8
--- /dev/null
+++ b/storage/2018-11-09/queue/messages/models.go
@@ -0,0 +1,21 @@
+package messages
+
+import "github.com/Azure/go-autorest/autorest"
+
+type QueueMessage struct {
+ MessageText string `xml:"MessageText"`
+}
+
+type QueueMessagesListResult struct {
+ autorest.Response
+
+ QueueMessages *[]QueueMessageResponse `xml:"QueueMessage"`
+}
+
+type QueueMessageResponse struct {
+ MessageId string `xml:"MessageId"`
+ InsertionTime string `xml:"InsertionTime"`
+ ExpirationTime string `xml:"ExpirationTime"`
+ PopReceipt string `xml:"PopReceipt"`
+ TimeNextVisible string `xml:"TimeNextVisible"`
+}
diff --git a/storage/2018-11-09/queue/messages/peek.go b/storage/2018-11-09/queue/messages/peek.go
new file mode 100644
index 0000000..7288bd5
--- /dev/null
+++ b/storage/2018-11-09/queue/messages/peek.go
@@ -0,0 +1,95 @@
+package messages
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Peek retrieves one or more messages from the front of the queue, but doesn't alter the visibility of the messages
+func (client Client) Peek(ctx context.Context, accountName, queueName string, numberOfMessages int) (result QueueMessagesListResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("messages.Client", "Peek", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("messages.Client", "Peek", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("messages.Client", "Peek", "`queueName` must be a lower-cased string.")
+ }
+ if numberOfMessages < 1 || numberOfMessages > 32 {
+ return result, validation.NewError("messages.Client", "Peek", "`numberOfMessages` must be between 1 and 32.")
+ }
+
+ req, err := client.PeekPreparer(ctx, accountName, queueName, numberOfMessages)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Peek", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PeekSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "messages.Client", "Peek", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PeekResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Peek", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PeekPreparer prepares the Peek request.
+func (client Client) PeekPreparer(ctx context.Context, accountName, queueName string, numberOfMessages int) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "numofmessages": autorest.Encode("query", numberOfMessages),
+ "peekonly": autorest.Encode("query", true),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}/messages", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PeekSender sends the Peek request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PeekSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PeekResponder handles the response to the Peek request. The method always
+// closes the http.Response Body.
+func (client Client) PeekResponder(resp *http.Response) (result QueueMessagesListResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ autorest.ByUnmarshallingXML(&result),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/queue/messages/put.go b/storage/2018-11-09/queue/messages/put.go
new file mode 100644
index 0000000..612b4a1
--- /dev/null
+++ b/storage/2018-11-09/queue/messages/put.go
@@ -0,0 +1,120 @@
+package messages
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type PutInput struct {
+ // A message must be in a format that can be included in an XML request with UTF-8 encoding.
+ // The encoded message can be up to 64 KB in size.
+ Message string
+
+ // The maximum time-to-live can be any positive number,
+ // as well as -1 indicating that the message does not expire.
+ // If this parameter is omitted, the default time-to-live is 7 days.
+ MessageTtl *int
+
+ // Specifies the new visibility timeout value, in seconds, relative to server time.
+ // The new value must be larger than or equal to 0, and cannot be larger than 7 days.
+ // The visibility timeout of a message cannot be set to a value later than the expiry time.
+ // visibilitytimeout should be set to a value smaller than the time-to-live value.
+ // If not specified, the default value is 0.
+ VisibilityTimeout *int
+}
+
+// Put adds a new message to the back of the message queue
+func (client Client) Put(ctx context.Context, accountName, queueName string, input PutInput) (result QueueMessagesListResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("messages.Client", "Put", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("messages.Client", "Put", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("messages.Client", "Put", "`queueName` must be a lower-cased string.")
+ }
+
+ req, err := client.PutPreparer(ctx, accountName, queueName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Put", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.PutSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "messages.Client", "Put", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.PutResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Put", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// PutPreparer prepares the Put request.
+func (client Client) PutPreparer(ctx context.Context, accountName, queueName string, input PutInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ queryParameters := map[string]interface{}{}
+
+ if input.MessageTtl != nil {
+ queryParameters["messagettl"] = autorest.Encode("path", *input.MessageTtl)
+ }
+
+ if input.VisibilityTimeout != nil {
+ queryParameters["visibilitytimeout"] = autorest.Encode("path", *input.VisibilityTimeout)
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ body := QueueMessage{
+ MessageText: input.Message,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPost(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}/messages", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithXML(body),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// PutSender sends the Put request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) PutSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// PutResponder handles the response to the Put request. The method always
+// closes the http.Response Body.
+func (client Client) PutResponder(resp *http.Response) (result QueueMessagesListResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ autorest.ByUnmarshallingXML(&result),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/queue/messages/resource_id.go b/storage/2018-11-09/queue/messages/resource_id.go
new file mode 100644
index 0000000..7ece98a
--- /dev/null
+++ b/storage/2018-11-09/queue/messages/resource_id.go
@@ -0,0 +1,56 @@
+package messages
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Message within a Queue
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, queueName, messageID string) string {
+ domain := endpoints.GetQueueEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s/messages/%s", domain, queueName, messageID)
+}
+
+type ResourceID struct {
+ AccountName string
+ QueueName string
+ MessageID string
+}
+
+// ParseResourceID parses the specified Resource ID and returns an object
+// which can be used to interact with the Message within a Queue
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://account1.queue.core.chinacloudapi.cn/queue1/messages/message1
+
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ path := strings.TrimPrefix(uri.Path, "/")
+ segments := strings.Split(path, "/")
+ if len(segments) != 3 {
+ return nil, fmt.Errorf("Expected the path to contain 3 segments but got %d", len(segments))
+ }
+
+ queueName := segments[0]
+ messageID := segments[2]
+ return &ResourceID{
+ AccountName: *accountName,
+ MessageID: messageID,
+ QueueName: queueName,
+ }, nil
+}
diff --git a/storage/2018-11-09/queue/messages/resource_id_test.go b/storage/2018-11-09/queue/messages/resource_id_test.go
new file mode 100644
index 0000000..5053279
--- /dev/null
+++ b/storage/2018-11-09/queue/messages/resource_id_test.go
@@ -0,0 +1,81 @@
+package messages
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.queue.core.chinacloudapi.cn/queue1/messages/message1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.queue.core.cloudapi.de/queue1/messages/message1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.queue.core.windows.net/queue1/messages/message1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.queue.core.usgovcloudapi.net/queue1/messages/message1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "queue1", "message1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.queue.core.chinacloudapi.cn/queue1/messages/message1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.queue.core.cloudapi.de/queue1/messages/message1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.queue.core.windows.net/queue1/messages/message1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.queue.core.usgovcloudapi.net/queue1/messages/message1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.QueueName != "queue1" {
+ t.Fatalf("Expected Queue Name to be `queue1` but got %q", actual.QueueName)
+ }
+ if actual.MessageID != "message1" {
+ t.Fatalf("Expected Message ID to be `message1` but got %q", actual.MessageID)
+ }
+ }
+}
diff --git a/storage/2018-11-09/queue/messages/update.go b/storage/2018-11-09/queue/messages/update.go
new file mode 100644
index 0000000..fb10fad
--- /dev/null
+++ b/storage/2018-11-09/queue/messages/update.go
@@ -0,0 +1,115 @@
+package messages
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type UpdateInput struct {
+ // A message must be in a format that can be included in an XML request with UTF-8 encoding.
+ // The encoded message can be up to 64 KB in size.
+ Message string
+
+ // Specifies the valid pop receipt value required to modify this message.
+ PopReceipt string
+
+ // Specifies the new visibility timeout value, in seconds, relative to server time.
+ // The new value must be larger than or equal to 0, and cannot be larger than 7 days.
+ // The visibility timeout of a message cannot be set to a value later than the expiry time.
+ // A message can be updated until it has been deleted or has expired.
+ VisibilityTimeout int
+}
+
+// Update updates an existing message based on it's Pop Receipt
+func (client Client) Update(ctx context.Context, accountName, queueName string, messageID string, input UpdateInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("messages.Client", "Update", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("messages.Client", "Update", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("messages.Client", "Update", "`queueName` must be a lower-cased string.")
+ }
+ if input.PopReceipt == "" {
+ return result, validation.NewError("messages.Client", "Update", "`input.PopReceipt` cannot be an empty string.")
+ }
+
+ req, err := client.UpdatePreparer(ctx, accountName, queueName, messageID, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Update", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.UpdateSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "messages.Client", "Update", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.UpdateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "messages.Client", "Update", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// UpdatePreparer prepares the Update request.
+func (client Client) UpdatePreparer(ctx context.Context, accountName, queueName string, messageID string, input UpdateInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ "messageID": autorest.Encode("path", messageID),
+ }
+
+ queryParameters := map[string]interface{}{
+ "popreceipt": autorest.Encode("query", input.PopReceipt),
+ "visibilitytimeout": autorest.Encode("query", input.VisibilityTimeout),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ body := QueueMessage{
+ MessageText: input.Message,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}/messages/{messageID}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithXML(body),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// UpdateSender sends the Update request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) UpdateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// UpdateResponder handles the response to the Update request. The method always
+// closes the http.Response Body.
+func (client Client) UpdateResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/queue/messages/version.go b/storage/2018-11-09/queue/messages/version.go
new file mode 100644
index 0000000..87c2420
--- /dev/null
+++ b/storage/2018-11-09/queue/messages/version.go
@@ -0,0 +1,14 @@
+package messages
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2018-11-09"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2018-11-09/queue/queues/README.md b/storage/2018-11-09/queue/queues/README.md
new file mode 100644
index 0000000..1769bfa
--- /dev/null
+++ b/storage/2018-11-09/queue/queues/README.md
@@ -0,0 +1,43 @@
+## Queue Storage Queues SDK for API version 2018-11-09
+
+This package allows you to interact with the Queues Queue Storage API
+
+### Supported Authorizers
+
+* Azure Active Directory (for the Resource Endpoint `https://storage.azure.com`)
+* SharedKeyLite (Blob, File & Queue)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/queue/queues"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ queueName := "myqueue"
+
+ storageAuth := autorest.NewSharedKeyLiteAuthorizer(accountName, storageAccountKey)
+ queuesClient := queues.New()
+ queuesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ metadata := map[string]string{
+ "hello": "world",
+ }
+ if _, err := queuesClient.Create(ctx, accountName, queueName, metadata); err != nil {
+ return fmt.Errorf("Error creating Queue: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2018-11-09/queue/queues/client.go b/storage/2018-11-09/queue/queues/client.go
new file mode 100644
index 0000000..2f80085
--- /dev/null
+++ b/storage/2018-11-09/queue/queues/client.go
@@ -0,0 +1,25 @@
+package queues
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for Queue Storage Shares.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2018-11-09/queue/queues/create.go b/storage/2018-11-09/queue/queues/create.go
new file mode 100644
index 0000000..f18910a
--- /dev/null
+++ b/storage/2018-11-09/queue/queues/create.go
@@ -0,0 +1,92 @@
+package queues
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// Create creates the specified Queue within the specified Storage Account
+func (client Client) Create(ctx context.Context, accountName, queueName string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("queues.Client", "Create", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("queues.Client", "Create", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("queues.Client", "Create", "`queueName` must be a lower-cased string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("queues.Client", "Create", fmt.Sprintf("`metadata` is not valid: %s.", err))
+ }
+
+ req, err := client.CreatePreparer(ctx, accountName, queueName, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "Create", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "queues.Client", "Create", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "Create", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreatePreparer prepares the Create request.
+func (client Client) CreatePreparer(ctx context.Context, accountName string, queueName string, metaData map[string]string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSender sends the Create request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateResponder handles the response to the Create request. The method always
+// closes the http.Response Body.
+func (client Client) CreateResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusCreated),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/queue/queues/delete.go b/storage/2018-11-09/queue/queues/delete.go
new file mode 100644
index 0000000..5f70595
--- /dev/null
+++ b/storage/2018-11-09/queue/queues/delete.go
@@ -0,0 +1,85 @@
+package queues
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete deletes the specified Queue within the specified Storage Account
+func (client Client) Delete(ctx context.Context, accountName, queueName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("queues.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("queues.Client", "Delete", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("queues.Client", "Delete", "`queueName` must be a lower-cased string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, queueName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "queues.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName string, queueName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/queue/queues/lifecycle_test.go b/storage/2018-11-09/queue/queues/lifecycle_test.go
new file mode 100644
index 0000000..ff720f6
--- /dev/null
+++ b/storage/2018-11-09/queue/queues/lifecycle_test.go
@@ -0,0 +1,155 @@
+package queues
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestQueuesLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ queueName := fmt.Sprintf("queue-%d", testhelpers.RandomInt())
+
+ _, err = client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ queuesClient := NewWithEnvironment(client.Environment)
+ queuesClient.Client = client.PrepareWithStorageResourceManagerAuth(queuesClient.Client)
+
+ // first let's test an empty container
+ _, err = queuesClient.Create(ctx, accountName, queueName, map[string]string{})
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error creating: %s", err))
+ }
+
+ // then let's retrieve it to ensure there's no metadata..
+ resp, err := queuesClient.GetMetaData(ctx, accountName, queueName)
+ if err != nil {
+ t.Fatalf("Error retrieving MetaData: %s", err)
+ }
+ if len(resp.MetaData) != 0 {
+ t.Fatalf("Expected no MetaData but got: %s", err)
+ }
+
+ // then let's add some..
+ updatedMetaData := map[string]string{
+ "band": "panic",
+ "boots": "the-overpass",
+ }
+ _, err = queuesClient.SetMetaData(ctx, accountName, queueName, updatedMetaData)
+ if err != nil {
+ t.Fatalf("Error setting MetaData: %s", err)
+ }
+
+ resp, err = queuesClient.GetMetaData(ctx, accountName, queueName)
+ if err != nil {
+ t.Fatalf("Error re-retrieving MetaData: %s", err)
+ }
+
+ if len(resp.MetaData) != 2 {
+ t.Fatalf("Expected metadata to have 2 items but got: %s", resp.MetaData)
+ }
+ if resp.MetaData["band"] != "panic" {
+ t.Fatalf("Expected `band` to be `panic` but got: %s", resp.MetaData["band"])
+ }
+ if resp.MetaData["boots"] != "the-overpass" {
+ t.Fatalf("Expected `boots` to be `the-overpass` but got: %s", resp.MetaData["boots"])
+ }
+
+ // and woo let's remove it again
+ _, err = queuesClient.SetMetaData(ctx, accountName, queueName, map[string]string{})
+ if err != nil {
+ t.Fatalf("Error setting MetaData: %s", err)
+ }
+
+ resp, err = queuesClient.GetMetaData(ctx, accountName, queueName)
+ if err != nil {
+ t.Fatalf("Error retrieving MetaData: %s", err)
+ }
+ if len(resp.MetaData) != 0 {
+ t.Fatalf("Expected no MetaData but got: %s", err)
+ }
+
+ // set some properties
+ props := StorageServiceProperties{
+ Logging: &LoggingConfig{
+ Version: "1.0",
+ Delete: true,
+ Read: true,
+ Write: true,
+ RetentionPolicy: RetentionPolicy{
+ Enabled: true,
+ Days: 7,
+ },
+ },
+ Cors: &Cors{
+ CorsRule: CorsRule{
+ AllowedMethods: "GET,PUT",
+ AllowedOrigins: "http://www.example.com",
+ ExposedHeaders: "x-tempo-*",
+ AllowedHeaders: "x-tempo-*",
+ MaxAgeInSeconds: 500,
+ },
+ },
+ HourMetrics: &MetricsConfig{
+ Version: "1.0",
+ Enabled: false,
+ RetentionPolicy: RetentionPolicy{
+ Enabled: true,
+ Days: 7,
+ },
+ },
+ MinuteMetrics: &MetricsConfig{
+ Version: "1.0",
+ Enabled: false,
+ RetentionPolicy: RetentionPolicy{
+ Enabled: true,
+ Days: 7,
+ },
+ },
+ }
+ _, err = queuesClient.SetServiceProperties(ctx, accountName, props)
+ if err != nil {
+ t.Fatalf("SetServiceProperties failed: %s", err)
+ }
+
+ properties, err := queuesClient.GetServiceProperties(ctx, accountName)
+ if err != nil {
+ t.Fatalf("GetServiceProperties failed: %s", err)
+ }
+
+ if properties.Cors.CorsRule.AllowedMethods != "GET,PUT" {
+ t.Fatalf("CORS Methods weren't set!")
+ }
+
+ if properties.HourMetrics.Enabled {
+ t.Fatalf("HourMetrics were enabled when they shouldn't be!")
+ }
+
+ if properties.MinuteMetrics.Enabled {
+ t.Fatalf("MinuteMetrics were enabled when they shouldn't be!")
+ }
+
+ if !properties.Logging.Write {
+ t.Fatalf("Logging Write's was not enabled when they should be!")
+ }
+
+ log.Printf("[DEBUG] Deleting..")
+ _, err = queuesClient.Delete(ctx, accountName, queueName)
+ if err != nil {
+ t.Fatal(fmt.Errorf("Error deleting: %s", err))
+ }
+}
diff --git a/storage/2018-11-09/queue/queues/metadata_get.go b/storage/2018-11-09/queue/queues/metadata_get.go
new file mode 100644
index 0000000..9c230b6
--- /dev/null
+++ b/storage/2018-11-09/queue/queues/metadata_get.go
@@ -0,0 +1,101 @@
+package queues
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+type GetMetaDataResult struct {
+ autorest.Response
+
+ MetaData map[string]string
+}
+
+// GetMetaData returns the metadata for this Queue
+func (client Client) GetMetaData(ctx context.Context, accountName, queueName string) (result GetMetaDataResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("queues.Client", "GetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("queues.Client", "GetMetaData", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("queues.Client", "GetMetaData", "`queueName` must be a lower-cased string.")
+ }
+
+ req, err := client.GetMetaDataPreparer(ctx, accountName, queueName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "GetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetMetaDataSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "queues.Client", "GetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "GetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetMetaDataPreparer prepares the GetMetaData request.
+func (client Client) GetMetaDataPreparer(ctx context.Context, accountName, queueName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetMetaDataSender sends the GetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetMetaDataResponder handles the response to the GetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) GetMetaDataResponder(resp *http.Response) (result GetMetaDataResult, err error) {
+ if resp != nil {
+ result.MetaData = metadata.ParseFromHeaders(resp.Header)
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/queue/queues/metadata_set.go b/storage/2018-11-09/queue/queues/metadata_set.go
new file mode 100644
index 0000000..51154a5
--- /dev/null
+++ b/storage/2018-11-09/queue/queues/metadata_set.go
@@ -0,0 +1,97 @@
+package queues
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+ "github.com/tombuildsstuff/giovanni/storage/internal/metadata"
+)
+
+// SetMetaData returns the metadata for this Queue
+func (client Client) SetMetaData(ctx context.Context, accountName, queueName string, metaData map[string]string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("queues.Client", "SetMetaData", "`accountName` cannot be an empty string.")
+ }
+ if queueName == "" {
+ return result, validation.NewError("queues.Client", "SetMetaData", "`queueName` cannot be an empty string.")
+ }
+ if strings.ToLower(queueName) != queueName {
+ return result, validation.NewError("queues.Client", "SetMetaData", "`queueName` must be a lower-cased string.")
+ }
+ if err := metadata.Validate(metaData); err != nil {
+ return result, validation.NewError("queues.Client", "SetMetaData", fmt.Sprintf("`metadata` is not valid: %s.", err))
+ }
+
+ req, err := client.SetMetaDataPreparer(ctx, accountName, queueName, metaData)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetMetaData", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetMetaDataSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetMetaData", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetMetaDataResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetMetaData", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetMetaDataPreparer prepares the SetMetaData request.
+func (client Client) SetMetaDataPreparer(ctx context.Context, accountName, queueName string, metaData map[string]string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "queueName": autorest.Encode("path", queueName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "metadata"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ headers = metadata.SetIntoHeaders(headers, metaData)
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{queueName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetMetaDataSender sends the SetMetaData request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetMetaDataSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetMetaDataResponder handles the response to the SetMetaData request. The method always
+// closes the http.Response Body.
+func (client Client) SetMetaDataResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/queue/queues/models.go b/storage/2018-11-09/queue/queues/models.go
new file mode 100644
index 0000000..89c2380
--- /dev/null
+++ b/storage/2018-11-09/queue/queues/models.go
@@ -0,0 +1,42 @@
+package queues
+
+type StorageServiceProperties struct {
+ Logging *LoggingConfig `xml:"Logging,omitempty"`
+ HourMetrics *MetricsConfig `xml:"HourMetrics,omitempty"`
+ MinuteMetrics *MetricsConfig `xml:"MinuteMetrics,omitempty"`
+ Cors *Cors `xml:"Cors,omitempty"`
+}
+
+type LoggingConfig struct {
+ Version string `xml:"Version"`
+ Delete bool `xml:"Delete"`
+ Read bool `xml:"Read"`
+ Write bool `xml:"Write"`
+ RetentionPolicy RetentionPolicy `xml:"RetentionPolicy"`
+}
+
+type MetricsConfig struct {
+ Version string `xml:"Version"`
+ Enabled bool `xml:"Enabled"`
+ RetentionPolicy RetentionPolicy `xml:"RetentionPolicy"`
+
+ // Element IncludeAPIs is only expected when Metrics is enabled
+ IncludeAPIs *bool `xml:"IncludeAPIs,omitempty"`
+}
+
+type RetentionPolicy struct {
+ Enabled bool `xml:"Enabled"`
+ Days int `xml:"Days"`
+}
+
+type Cors struct {
+ CorsRule CorsRule `xml:"CorsRule"`
+}
+
+type CorsRule struct {
+ AllowedOrigins string `xml:"AllowedOrigins"`
+ AllowedMethods string `xml:"AllowedMethods"`
+ AllowedHeaders string `xml:"AllowedHeaders`
+ ExposedHeaders string `xml:"ExposedHeaders"`
+ MaxAgeInSeconds int `xml:"MaxAgeInSeconds"`
+}
diff --git a/storage/2018-11-09/queue/queues/properties_get.go b/storage/2018-11-09/queue/queues/properties_get.go
new file mode 100644
index 0000000..9d17fb2
--- /dev/null
+++ b/storage/2018-11-09/queue/queues/properties_get.go
@@ -0,0 +1,85 @@
+package queues
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type StorageServicePropertiesResponse struct {
+ StorageServiceProperties
+ autorest.Response
+}
+
+// SetServiceProperties gets the properties for this queue
+func (client Client) GetServiceProperties(ctx context.Context, accountName string) (result StorageServicePropertiesResponse, err error) {
+ if accountName == "" {
+ return result, validation.NewError("queues.Client", "SetServiceProperties", "`accountName` cannot be an empty string.")
+ }
+
+ req, err := client.GetServicePropertiesPreparer(ctx, accountName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetServiceProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetServicePropertiesSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetServiceProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetServicePropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetServiceProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetServicePropertiesPreparer prepares the GetServiceProperties request.
+func (client Client) GetServicePropertiesPreparer(ctx context.Context, accountName string) (*http.Request, error) {
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "properties"),
+ "restype": autorest.Encode("path", "service"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetServicePropertiesSender sends the GetServiceProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetServicePropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetServicePropertiesResponder handles the response to the GetServiceProperties request. The method always
+// closes the http.Response Body.
+func (client Client) GetServicePropertiesResponder(resp *http.Response) (result StorageServicePropertiesResponse, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/queue/queues/properties_set.go b/storage/2018-11-09/queue/queues/properties_set.go
new file mode 100644
index 0000000..d6f6392
--- /dev/null
+++ b/storage/2018-11-09/queue/queues/properties_set.go
@@ -0,0 +1,80 @@
+package queues
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// SetServiceProperties sets the properties for this queue
+func (client Client) SetServiceProperties(ctx context.Context, accountName string, properties StorageServiceProperties) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("queues.Client", "SetServiceProperties", "`accountName` cannot be an empty string.")
+ }
+
+ req, err := client.SetServicePropertiesPreparer(ctx, accountName, properties)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetServiceProperties", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetServicePropertiesSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetServiceProperties", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetServicePropertiesResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "queues.Client", "SetServiceProperties", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetServicePropertiesPreparer prepares the SetServiceProperties request.
+func (client Client) SetServicePropertiesPreparer(ctx context.Context, accountName string, properties StorageServiceProperties) (*http.Request, error) {
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("path", "properties"),
+ "restype": autorest.Encode("path", "service"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetQueueEndpoint(client.BaseURI, accountName)),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithXML(properties),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetServicePropertiesSender sends the SetServiceProperties request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetServicePropertiesSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetServicePropertiesResponder handles the response to the SetServiceProperties request. The method always
+// closes the http.Response Body.
+func (client Client) SetServicePropertiesResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusAccepted),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/queue/queues/resource_id.go b/storage/2018-11-09/queue/queues/resource_id.go
new file mode 100644
index 0000000..ee28b8b
--- /dev/null
+++ b/storage/2018-11-09/queue/queues/resource_id.go
@@ -0,0 +1,46 @@
+package queues
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Queue
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, queueName string) string {
+ domain := endpoints.GetQueueEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s", domain, queueName)
+}
+
+type ResourceID struct {
+ AccountName string
+ QueueName string
+}
+
+// ParseResourceID parses the Resource ID and returns an Object which
+// can be used to interact with a Queue within a Storage Account
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://foo.queue.core.windows.net/Bar
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ queueName := strings.TrimPrefix(uri.Path, "/")
+ return &ResourceID{
+ AccountName: *accountName,
+ QueueName: queueName,
+ }, nil
+}
diff --git a/storage/2018-11-09/queue/queues/resource_id_test.go b/storage/2018-11-09/queue/queues/resource_id_test.go
new file mode 100644
index 0000000..89323d7
--- /dev/null
+++ b/storage/2018-11-09/queue/queues/resource_id_test.go
@@ -0,0 +1,79 @@
+package queues
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.queue.core.chinacloudapi.cn/queue1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.queue.core.cloudapi.de/queue1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.queue.core.windows.net/queue1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.queue.core.usgovcloudapi.net/queue1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "queue1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.queue.core.chinacloudapi.cn/queue1",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.queue.core.cloudapi.de/queue1",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.queue.core.windows.net/queue1",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.queue.core.usgovcloudapi.net/queue1",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected the account name to be `account1` but got %q", actual.AccountName)
+ }
+
+ if actual.QueueName != "queue1" {
+ t.Fatalf("Expected the queue name to be `queue1` but got %q", actual.QueueName)
+ }
+ }
+}
diff --git a/storage/2018-11-09/queue/queues/version.go b/storage/2018-11-09/queue/queues/version.go
new file mode 100644
index 0000000..13c7d2f
--- /dev/null
+++ b/storage/2018-11-09/queue/queues/version.go
@@ -0,0 +1,14 @@
+package queues
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2018-11-09"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2018-11-09/table/entities/README.md b/storage/2018-11-09/table/entities/README.md
new file mode 100644
index 0000000..06c2cb8
--- /dev/null
+++ b/storage/2018-11-09/table/entities/README.md
@@ -0,0 +1,48 @@
+## Table Storage Entities SDK for API version 2018-11-09
+
+This package allows you to interact with the Entities Table Storage API
+
+### Supported Authorizers
+
+* SharedKeyLite (Table)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/table/entities"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ tableName := "mytable"
+
+ storageAuth := autorest.NewSharedKeyLiteTableAuthorizer(accountName, storageAccountKey)
+ entitiesClient := entities.New()
+ entitiesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ input := entities.InsertEntityInput{
+ PartitionKey: "abc",
+ RowKey: "123",
+ MetaDataLevel: entities.NoMetaData,
+ Entity: map[string]interface{}{
+ "title": "Don't Kill My Vibe",
+ "artist": "Sigrid",
+ },
+ }
+ if _, err := entitiesClient.Insert(ctx, accountName, tableName, input); err != nil {
+ return fmt.Errorf("Error creating Entity: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2018-11-09/table/entities/client.go b/storage/2018-11-09/table/entities/client.go
new file mode 100644
index 0000000..17e9d75
--- /dev/null
+++ b/storage/2018-11-09/table/entities/client.go
@@ -0,0 +1,25 @@
+package entities
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for Table Storage Shares.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2018-11-09/table/entities/delete.go b/storage/2018-11-09/table/entities/delete.go
new file mode 100644
index 0000000..83e9188
--- /dev/null
+++ b/storage/2018-11-09/table/entities/delete.go
@@ -0,0 +1,99 @@
+package entities
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type DeleteEntityInput struct {
+ // When inserting an entity into a table, you must specify values for the PartitionKey and RowKey system properties.
+ // Together, these properties form the primary key and must be unique within the table.
+ // Both the PartitionKey and RowKey values must be string values; each key value may be up to 64 KB in size.
+ // If you are using an integer value for the key value, you should convert the integer to a fixed-width string,
+ // because they are canonically sorted. For example, you should convert the value 1 to 0000001 to ensure proper sorting.
+ RowKey string
+ PartitionKey string
+}
+
+// Delete deletes an existing entity in a table.
+func (client Client) Delete(ctx context.Context, accountName, tableName string, input DeleteEntityInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("entities.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("entities.Client", "Delete", "`tableName` cannot be an empty string.")
+ }
+ if input.PartitionKey == "" {
+ return result, validation.NewError("entities.Client", "Delete", "`input.PartitionKey` cannot be an empty string.")
+ }
+ if input.RowKey == "" {
+ return result, validation.NewError("entities.Client", "Delete", "`input.RowKey` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, tableName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "entities.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, tableName string, input DeleteEntityInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ "partitionKey": autorest.Encode("path", input.PartitionKey),
+ "rowKey": autorest.Encode("path", input.RowKey),
+ }
+
+ headers := map[string]interface{}{
+ // TODO: support for eTags
+ "If-Match": "*",
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}(PartitionKey='{partitionKey}', RowKey='{rowKey}')", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/table/entities/get.go b/storage/2018-11-09/table/entities/get.go
new file mode 100644
index 0000000..bdb4018
--- /dev/null
+++ b/storage/2018-11-09/table/entities/get.go
@@ -0,0 +1,108 @@
+package entities
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetEntityInput struct {
+ PartitionKey string
+ RowKey string
+
+ // The Level of MetaData which should be returned
+ MetaDataLevel MetaDataLevel
+}
+
+type GetEntityResult struct {
+ autorest.Response
+
+ Entity map[string]interface{}
+}
+
+// Get queries entities in a table and includes the $filter and $select options.
+func (client Client) Get(ctx context.Context, accountName, tableName string, input GetEntityInput) (result GetEntityResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("entities.Client", "Get", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("entities.Client", "Get", "`tableName` cannot be an empty string.")
+ }
+ if input.PartitionKey == "" {
+ return result, validation.NewError("entities.Client", "Get", "`input.PartitionKey` cannot be an empty string.")
+ }
+ if input.RowKey == "" {
+ return result, validation.NewError("entities.Client", "Get", "`input.RowKey` cannot be an empty string.")
+ }
+
+ req, err := client.GetPreparer(ctx, accountName, tableName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Get", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "entities.Client", "Get", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Get", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetPreparer prepares the Get request.
+func (client Client) GetPreparer(ctx context.Context, accountName, tableName string, input GetEntityInput) (*http.Request, error) {
+
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ "partitionKey": autorest.Encode("path", input.PartitionKey),
+ "rowKey": autorest.Encode("path", input.RowKey),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "Accept": fmt.Sprintf("application/json;odata=%s", input.MetaDataLevel),
+ "DataServiceVersion": "3.0;NetFx",
+ "MaxDataServiceVersion": "3.0;NetFx",
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}(PartitionKey='{partitionKey}',RowKey='{rowKey}')", pathParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetSender sends the Get request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetResponder handles the response to the Get request. The method always
+// closes the http.Response Body.
+func (client Client) GetResponder(resp *http.Response) (result GetEntityResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingJSON(&result.Entity),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/table/entities/insert.go b/storage/2018-11-09/table/entities/insert.go
new file mode 100644
index 0000000..92b05ce
--- /dev/null
+++ b/storage/2018-11-09/table/entities/insert.go
@@ -0,0 +1,112 @@
+package entities
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type InsertEntityInput struct {
+ // The level of MetaData provided for this Entity
+ MetaDataLevel MetaDataLevel
+
+ // The Entity which should be inserted, by default all values are strings
+ // To explicitly type a property, specify the appropriate OData data type by setting
+ // the m:type attribute within the property definition
+ Entity map[string]interface{}
+
+ // When inserting an entity into a table, you must specify values for the PartitionKey and RowKey system properties.
+ // Together, these properties form the primary key and must be unique within the table.
+ // Both the PartitionKey and RowKey values must be string values; each key value may be up to 64 KB in size.
+ // If you are using an integer value for the key value, you should convert the integer to a fixed-width string,
+ // because they are canonically sorted. For example, you should convert the value 1 to 0000001 to ensure proper sorting.
+ RowKey string
+ PartitionKey string
+}
+
+// Insert inserts a new entity into a table.
+func (client Client) Insert(ctx context.Context, accountName, tableName string, input InsertEntityInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("entities.Client", "Insert", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("entities.Client", "Insert", "`tableName` cannot be an empty string.")
+ }
+ if input.PartitionKey == "" {
+ return result, validation.NewError("entities.Client", "Insert", "`input.PartitionKey` cannot be an empty string.")
+ }
+ if input.RowKey == "" {
+ return result, validation.NewError("entities.Client", "Insert", "`input.RowKey` cannot be an empty string.")
+ }
+
+ req, err := client.InsertPreparer(ctx, accountName, tableName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Insert", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.InsertSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "entities.Client", "Insert", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.InsertResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Insert", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// InsertPreparer prepares the Insert request.
+func (client Client) InsertPreparer(ctx context.Context, accountName, tableName string, input InsertEntityInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "Accept": fmt.Sprintf("application/json;odata=%s", input.MetaDataLevel),
+ "Prefer": "return-no-content",
+ }
+
+ input.Entity["PartitionKey"] = input.PartitionKey
+ input.Entity["RowKey"] = input.RowKey
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/json"),
+ autorest.AsPost(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}", pathParameters),
+ autorest.WithJSON(input.Entity),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// InsertSender sends the Insert request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) InsertSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// InsertResponder handles the response to the Insert request. The method always
+// closes the http.Response Body.
+func (client Client) InsertResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/table/entities/insert_or_merge.go b/storage/2018-11-09/table/entities/insert_or_merge.go
new file mode 100644
index 0000000..1fb4ed3
--- /dev/null
+++ b/storage/2018-11-09/table/entities/insert_or_merge.go
@@ -0,0 +1,108 @@
+package entities
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type InsertOrMergeEntityInput struct {
+ // The Entity which should be inserted, by default all values are strings
+ // To explicitly type a property, specify the appropriate OData data type by setting
+ // the m:type attribute within the property definition
+ Entity map[string]interface{}
+
+ // When inserting an entity into a table, you must specify values for the PartitionKey and RowKey system properties.
+ // Together, these properties form the primary key and must be unique within the table.
+ // Both the PartitionKey and RowKey values must be string values; each key value may be up to 64 KB in size.
+ // If you are using an integer value for the key value, you should convert the integer to a fixed-width string,
+ // because they are canonically sorted. For example, you should convert the value 1 to 0000001 to ensure proper sorting.
+ RowKey string
+ PartitionKey string
+}
+
+// InsertOrMerge updates an existing entity or inserts a new entity if it does not exist in the table.
+// Because this operation can insert or update an entity, it is also known as an upsert operation.
+func (client Client) InsertOrMerge(ctx context.Context, accountName, tableName string, input InsertOrMergeEntityInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("entities.Client", "InsertOrMerge", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("entities.Client", "InsertOrMerge", "`tableName` cannot be an empty string.")
+ }
+ if input.PartitionKey == "" {
+ return result, validation.NewError("entities.Client", "InsertOrMerge", "`input.PartitionKey` cannot be an empty string.")
+ }
+ if input.RowKey == "" {
+ return result, validation.NewError("entities.Client", "InsertOrMerge", "`input.RowKey` cannot be an empty string.")
+ }
+
+ req, err := client.InsertOrMergePreparer(ctx, accountName, tableName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "InsertOrMerge", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.InsertOrMergeSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "entities.Client", "InsertOrMerge", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.InsertOrMergeResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "InsertOrMerge", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// InsertOrMergePreparer prepares the InsertOrMerge request.
+func (client Client) InsertOrMergePreparer(ctx context.Context, accountName, tableName string, input InsertOrMergeEntityInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ "partitionKey": autorest.Encode("path", input.PartitionKey),
+ "rowKey": autorest.Encode("path", input.RowKey),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "Accept": "application/json",
+ "Prefer": "return-no-content",
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/json"),
+ autorest.AsMerge(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}(PartitionKey='{partitionKey}', RowKey='{rowKey}')", pathParameters),
+ autorest.WithJSON(input.Entity),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// InsertOrMergeSender sends the InsertOrMerge request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) InsertOrMergeSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// InsertOrMergeResponder handles the response to the InsertOrMerge request. The method always
+// closes the http.Response Body.
+func (client Client) InsertOrMergeResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/table/entities/insert_or_replace.go b/storage/2018-11-09/table/entities/insert_or_replace.go
new file mode 100644
index 0000000..036ba5d
--- /dev/null
+++ b/storage/2018-11-09/table/entities/insert_or_replace.go
@@ -0,0 +1,108 @@
+package entities
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type InsertOrReplaceEntityInput struct {
+ // The Entity which should be inserted, by default all values are strings
+ // To explicitly type a property, specify the appropriate OData data type by setting
+ // the m:type attribute within the property definition
+ Entity map[string]interface{}
+
+ // When inserting an entity into a table, you must specify values for the PartitionKey and RowKey system properties.
+ // Together, these properties form the primary key and must be unique within the table.
+ // Both the PartitionKey and RowKey values must be string values; each key value may be up to 64 KB in size.
+ // If you are using an integer value for the key value, you should convert the integer to a fixed-width string,
+ // because they are canonically sorted. For example, you should convert the value 1 to 0000001 to ensure proper sorting.
+ RowKey string
+ PartitionKey string
+}
+
+// InsertOrReplace replaces an existing entity or inserts a new entity if it does not exist in the table.
+// Because this operation can insert or update an entity, it is also known as an upsert operation.
+func (client Client) InsertOrReplace(ctx context.Context, accountName, tableName string, input InsertOrReplaceEntityInput) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("entities.Client", "InsertOrReplace", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("entities.Client", "InsertOrReplace", "`tableName` cannot be an empty string.")
+ }
+ if input.PartitionKey == "" {
+ return result, validation.NewError("entities.Client", "InsertOrReplace", "`input.PartitionKey` cannot be an empty string.")
+ }
+ if input.RowKey == "" {
+ return result, validation.NewError("entities.Client", "InsertOrReplace", "`input.RowKey` cannot be an empty string.")
+ }
+
+ req, err := client.InsertOrReplacePreparer(ctx, accountName, tableName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "InsertOrReplace", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.InsertOrReplaceSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "entities.Client", "InsertOrReplace", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.InsertOrReplaceResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "InsertOrReplace", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// InsertOrReplacePreparer prepares the InsertOrReplace request.
+func (client Client) InsertOrReplacePreparer(ctx context.Context, accountName, tableName string, input InsertOrReplaceEntityInput) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ "partitionKey": autorest.Encode("path", input.PartitionKey),
+ "rowKey": autorest.Encode("path", input.RowKey),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "Accept": "application/json",
+ "Prefer": "return-no-content",
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/json"),
+ autorest.AsMerge(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}(PartitionKey='{partitionKey}', RowKey='{rowKey}')", pathParameters),
+ autorest.WithJSON(input.Entity),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// InsertOrReplaceSender sends the InsertOrReplace request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) InsertOrReplaceSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// InsertOrReplaceResponder handles the response to the InsertOrReplace request. The method always
+// closes the http.Response Body.
+func (client Client) InsertOrReplaceResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/table/entities/lifecycle_test.go b/storage/2018-11-09/table/entities/lifecycle_test.go
new file mode 100644
index 0000000..237aa89
--- /dev/null
+++ b/storage/2018-11-09/table/entities/lifecycle_test.go
@@ -0,0 +1,135 @@
+package entities
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/table/tables"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestEntitiesLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ tableName := fmt.Sprintf("table%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteTableAuthorizer(accountName, testData.StorageAccountKey)
+ tablesClient := tables.NewWithEnvironment(client.Environment)
+ tablesClient.Client = client.PrepareWithAuthorizer(tablesClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Creating Table..")
+ if _, err := tablesClient.Create(ctx, accountName, tableName); err != nil {
+ t.Fatalf("Error creating Table %q: %s", tableName, err)
+ }
+ defer tablesClient.Delete(ctx, accountName, tableName)
+
+ entitiesClient := NewWithEnvironment(client.Environment)
+ entitiesClient.Client = client.PrepareWithAuthorizer(entitiesClient.Client, storageAuth)
+
+ partitionKey := "hello"
+ rowKey := "there"
+
+ t.Logf("[DEBUG] Inserting..")
+ insertInput := InsertEntityInput{
+ MetaDataLevel: NoMetaData,
+ PartitionKey: partitionKey,
+ RowKey: rowKey,
+ Entity: map[string]interface{}{
+ "hello": "world",
+ },
+ }
+ if _, err := entitiesClient.Insert(ctx, accountName, tableName, insertInput); err != nil {
+ t.Logf("Error retrieving: %s", err)
+ }
+
+ t.Logf("[DEBUG] Insert or Merging..")
+ insertOrMergeInput := InsertOrMergeEntityInput{
+ PartitionKey: partitionKey,
+ RowKey: rowKey,
+ Entity: map[string]interface{}{
+ "hello": "ther88e",
+ },
+ }
+ if _, err := entitiesClient.InsertOrMerge(ctx, accountName, tableName, insertOrMergeInput); err != nil {
+ t.Logf("Error insert/merging: %s", err)
+ }
+
+ t.Logf("[DEBUG] Insert or Replacing..")
+ insertOrReplaceInput := InsertOrReplaceEntityInput{
+ PartitionKey: partitionKey,
+ RowKey: rowKey,
+ Entity: map[string]interface{}{
+ "hello": "pandas",
+ },
+ }
+ if _, err := entitiesClient.InsertOrReplace(ctx, accountName, tableName, insertOrReplaceInput); err != nil {
+ t.Logf("Error inserting/replacing: %s", err)
+ }
+
+ t.Logf("[DEBUG] Querying..")
+ queryInput := QueryEntitiesInput{
+ MetaDataLevel: NoMetaData,
+ }
+ results, err := entitiesClient.Query(ctx, accountName, tableName, queryInput)
+ if err != nil {
+ t.Logf("Error querying: %s", err)
+ }
+
+ if len(results.Entities) != 1 {
+ t.Fatalf("Expected 1 item but got %d", len(results.Entities))
+ }
+
+ for _, v := range results.Entities {
+ thisPartitionKey := v["PartitionKey"].(string)
+ thisRowKey := v["RowKey"].(string)
+ if partitionKey != thisPartitionKey {
+ t.Fatalf("Expected Partition Key to be %q but got %q", partitionKey, thisPartitionKey)
+ }
+ if rowKey != thisRowKey {
+ t.Fatalf("Expected Partition Key to be %q but got %q", rowKey, thisRowKey)
+ }
+ }
+
+ t.Logf("[DEBUG] Retrieving..")
+ getInput := GetEntityInput{
+ MetaDataLevel: MinimalMetaData,
+ PartitionKey: partitionKey,
+ RowKey: rowKey,
+ }
+ getResults, err := entitiesClient.Get(ctx, accountName, tableName, getInput)
+ if err != nil {
+ t.Logf("Error querying: %s", err)
+ }
+
+ partitionKey2 := getResults.Entity["PartitionKey"].(string)
+ rowKey2 := getResults.Entity["RowKey"].(string)
+ if partitionKey2 != partitionKey {
+ t.Fatalf("Expected Partition Key to be %q but got %q", partitionKey, partitionKey2)
+ }
+ if rowKey2 != rowKey {
+ t.Fatalf("Expected Row Key to be %q but got %q", rowKey, rowKey2)
+ }
+
+ t.Logf("[DEBUG] Deleting..")
+ deleteInput := DeleteEntityInput{
+ PartitionKey: partitionKey,
+ RowKey: rowKey,
+ }
+ if _, err := entitiesClient.Delete(ctx, accountName, tableName, deleteInput); err != nil {
+ t.Logf("Error deleting: %s", err)
+ }
+}
diff --git a/storage/2018-11-09/table/entities/models.go b/storage/2018-11-09/table/entities/models.go
new file mode 100644
index 0000000..e3c6ccc
--- /dev/null
+++ b/storage/2018-11-09/table/entities/models.go
@@ -0,0 +1,9 @@
+package entities
+
+type MetaDataLevel string
+
+var (
+ NoMetaData MetaDataLevel = "nometadata"
+ MinimalMetaData MetaDataLevel = "minimalmetadata"
+ FullMetaData MetaDataLevel = "fullmetadata"
+)
diff --git a/storage/2018-11-09/table/entities/query.go b/storage/2018-11-09/table/entities/query.go
new file mode 100644
index 0000000..a768b83
--- /dev/null
+++ b/storage/2018-11-09/table/entities/query.go
@@ -0,0 +1,155 @@
+package entities
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type QueryEntitiesInput struct {
+ // An optional OData filter
+ Filter *string
+
+ // An optional comma-separated
+ PropertyNamesToSelect *[]string
+
+ PartitionKey string
+ RowKey string
+
+ // The Level of MetaData which should be returned
+ MetaDataLevel MetaDataLevel
+
+ // The Next Partition Key used to load data from a previous point
+ NextPartitionKey *string
+
+ // The Next Row Key used to load data from a previous point
+ NextRowKey *string
+}
+
+type QueryEntitiesResult struct {
+ autorest.Response
+
+ NextPartitionKey string
+ NextRowKey string
+
+ MetaData string `json:"odata.metadata,omitempty"`
+ Entities []map[string]interface{} `json:"value"`
+}
+
+// Query queries entities in a table and includes the $filter and $select options.
+func (client Client) Query(ctx context.Context, accountName, tableName string, input QueryEntitiesInput) (result QueryEntitiesResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("entities.Client", "Query", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("entities.Client", "Query", "`tableName` cannot be an empty string.")
+ }
+
+ req, err := client.QueryPreparer(ctx, accountName, tableName, input)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Query", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.QuerySender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "entities.Client", "Query", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.QueryResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "entities.Client", "Query", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// QueryPreparer prepares the Query request.
+func (client Client) QueryPreparer(ctx context.Context, accountName, tableName string, input QueryEntitiesInput) (*http.Request, error) {
+
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ "additionalParameters": "",
+ }
+
+ //PartitionKey='',RowKey=''
+ additionalParams := make([]string, 0)
+ if input.PartitionKey != "" {
+ additionalParams = append(additionalParams, fmt.Sprintf("PartitionKey='%s'", input.PartitionKey))
+ }
+ if input.RowKey != "" {
+ additionalParams = append(additionalParams, fmt.Sprintf("RowKey='%s'", input.RowKey))
+ }
+ if len(additionalParams) > 0 {
+ pathParameters["additionalParameters"] = autorest.Encode("path", strings.Join(additionalParams, ","))
+ }
+
+ queryParameters := map[string]interface{}{}
+
+ if input.Filter != nil {
+ queryParameters["filter"] = autorest.Encode("query", input.Filter)
+ }
+
+ if input.PropertyNamesToSelect != nil {
+ queryParameters["$select"] = autorest.Encode("query", strings.Join(*input.PropertyNamesToSelect, ","))
+ }
+
+ if input.NextPartitionKey != nil {
+ queryParameters["NextPartitionKey"] = *input.NextPartitionKey
+ }
+
+ if input.NextRowKey != nil {
+ queryParameters["NextRowKey"] = *input.NextRowKey
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "Accept": fmt.Sprintf("application/json;odata=%s", input.MetaDataLevel),
+ "DataServiceVersion": "3.0;NetFx",
+ "MaxDataServiceVersion": "3.0;NetFx",
+ }
+
+ // GET /myaccount/Customers()?$filter=(Rating%20ge%203)%20and%20(Rating%20le%206)&$select=PartitionKey,RowKey,Address,CustomerSince
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}({additionalParameters})", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// QuerySender sends the Query request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) QuerySender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// QueryResponder handles the response to the Query request. The method always
+// closes the http.Response Body.
+func (client Client) QueryResponder(resp *http.Response) (result QueryEntitiesResult, err error) {
+ if resp != nil && resp.Header != nil {
+ result.NextPartitionKey = resp.Header.Get("x-ms-continuation-NextPartitionKey")
+ result.NextRowKey = resp.Header.Get("x-ms-continuation-NextRowKey")
+ }
+
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingJSON(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/table/entities/resource_id.go b/storage/2018-11-09/table/entities/resource_id.go
new file mode 100644
index 0000000..59366a2
--- /dev/null
+++ b/storage/2018-11-09/table/entities/resource_id.go
@@ -0,0 +1,91 @@
+package entities
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Entity
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, tableName, partitionKey, rowKey string) string {
+ domain := endpoints.GetTableEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/%s(PartitionKey='%s',RowKey='%s')", domain, tableName, partitionKey, rowKey)
+}
+
+type ResourceID struct {
+ AccountName string
+ TableName string
+ PartitionKey string
+ RowKey string
+}
+
+// ParseResourceID parses the specified Resource ID and returns an object which
+// can be used to look up the specified Entity within the specified Table
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://account1.table.core.chinacloudapi.cn/table1(PartitionKey='partition1',RowKey='row1')
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ // assume there a `Table('')`
+ path := strings.TrimPrefix(uri.Path, "/")
+ if !strings.Contains(uri.Path, "(") || !strings.HasSuffix(uri.Path, ")") {
+ return nil, fmt.Errorf("Expected the Table Name to be in the format `tables(PartitionKey='',RowKey='')` but got %q", path)
+ }
+
+ // NOTE: honestly this could probably be a RegEx, but this seemed like the simplest way to
+ // allow these two fields to be specified in either order
+ indexOfBracket := strings.IndexByte(path, '(')
+ tableName := path[0:indexOfBracket]
+
+ // trim off the brackets
+ temp := strings.TrimPrefix(path, fmt.Sprintf("%s(", tableName))
+ temp = strings.TrimSuffix(temp, ")")
+
+ dictionary := strings.Split(temp, ",")
+ partitionKey := ""
+ rowKey := ""
+ for _, v := range dictionary {
+ split := strings.Split(v, "=")
+ if len(split) != 2 {
+ return nil, fmt.Errorf("Expected 2 segments but got %d for %q", len(split), v)
+ }
+
+ key := split[0]
+ value := strings.TrimSuffix(strings.TrimPrefix(split[1], "'"), "'")
+ if strings.EqualFold(key, "PartitionKey") {
+ partitionKey = value
+ } else if strings.EqualFold(key, "RowKey") {
+ rowKey = value
+ } else {
+ return nil, fmt.Errorf("Unexpected Key %q", key)
+ }
+ }
+
+ if partitionKey == "" {
+ return nil, fmt.Errorf("Expected a PartitionKey but didn't get one")
+ }
+ if rowKey == "" {
+ return nil, fmt.Errorf("Expected a RowKey but didn't get one")
+ }
+
+ return &ResourceID{
+ AccountName: *accountName,
+ TableName: tableName,
+ PartitionKey: partitionKey,
+ RowKey: rowKey,
+ }, nil
+}
diff --git a/storage/2018-11-09/table/entities/resource_id_test.go b/storage/2018-11-09/table/entities/resource_id_test.go
new file mode 100644
index 0000000..e85af79
--- /dev/null
+++ b/storage/2018-11-09/table/entities/resource_id_test.go
@@ -0,0 +1,84 @@
+package entities
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.table.core.chinacloudapi.cn/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.table.core.cloudapi.de/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.table.core.windows.net/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.table.core.usgovcloudapi.net/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "table1", "partition1", "row1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.table.core.chinacloudapi.cn/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.table.core.cloudapi.de/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.table.core.windows.net/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.table.core.usgovcloudapi.net/table1(PartitionKey='partition1',RowKey='row1')",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.TableName != "table1" {
+ t.Fatalf("Expected Table Name to be `table1` but got %q", actual.TableName)
+ }
+ if actual.PartitionKey != "partition1" {
+ t.Fatalf("Expected Partition Key to be `partition1` but got %q", actual.PartitionKey)
+ }
+ if actual.RowKey != "row1" {
+ t.Fatalf("Expected Row Key to be `row1` but got %q", actual.RowKey)
+ }
+ }
+}
diff --git a/storage/2018-11-09/table/entities/version.go b/storage/2018-11-09/table/entities/version.go
new file mode 100644
index 0000000..0914b24
--- /dev/null
+++ b/storage/2018-11-09/table/entities/version.go
@@ -0,0 +1,14 @@
+package entities
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2018-11-09"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/2018-11-09/table/tables/README.md b/storage/2018-11-09/table/tables/README.md
new file mode 100644
index 0000000..ee05271
--- /dev/null
+++ b/storage/2018-11-09/table/tables/README.md
@@ -0,0 +1,39 @@
+## Table Storage Tables SDK for API version 2018-11-09
+
+This package allows you to interact with the Tables Table Storage API
+
+### Supported Authorizers
+
+* SharedKeyLite (Table)
+
+### Example Usage
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/tombuildsstuff/giovanni/storage/2018-11-09/table/tables"
+)
+
+func Example() error {
+ accountName := "storageaccount1"
+ storageAccountKey := "ABC123...."
+ tableName := "mytable"
+
+ storageAuth := autorest.NewSharedKeyLiteTableAuthorizer(accountName, storageAccountKey)
+ tablesClient := tables.New()
+ tablesClient.Client.Authorizer = storageAuth
+
+ ctx := context.TODO()
+ if _, err := tablesClient.Insert(ctx, accountName, tableName); err != nil {
+ return fmt.Errorf("Error creating Table: %s", err)
+ }
+
+ return nil
+}
+```
\ No newline at end of file
diff --git a/storage/2018-11-09/table/tables/acl_get.go b/storage/2018-11-09/table/tables/acl_get.go
new file mode 100644
index 0000000..0ef0000
--- /dev/null
+++ b/storage/2018-11-09/table/tables/acl_get.go
@@ -0,0 +1,93 @@
+package tables
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetACLResult struct {
+ autorest.Response
+
+ SignedIdentifiers []SignedIdentifier `xml:"SignedIdentifier"`
+}
+
+// GetACL returns the Access Control List for the specified Table
+func (client Client) GetACL(ctx context.Context, accountName, tableName string) (result GetACLResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("tables.Client", "GetACL", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("tables.Client", "GetACL", "`tableName` cannot be an empty string.")
+ }
+
+ req, err := client.GetACLPreparer(ctx, accountName, tableName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "GetACL", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.GetACLSender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "tables.Client", "GetACL", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.GetACLResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "GetACL", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// GetACLPreparer prepares the GetACL request.
+func (client Client) GetACLPreparer(ctx context.Context, accountName, tableName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "acl"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// GetACLSender sends the GetACL request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) GetACLSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// GetACLResponder handles the response to the GetACL request. The method always
+// closes the http.Response Body.
+func (client Client) GetACLResponder(resp *http.Response) (result GetACLResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingXML(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/table/tables/acl_set.go b/storage/2018-11-09/table/tables/acl_set.go
new file mode 100644
index 0000000..c26bffc
--- /dev/null
+++ b/storage/2018-11-09/table/tables/acl_set.go
@@ -0,0 +1,98 @@
+package tables
+
+import (
+ "context"
+ "encoding/xml"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type setAcl struct {
+ SignedIdentifiers []SignedIdentifier `xml:"SignedIdentifier"`
+
+ XMLName xml.Name `xml:"SignedIdentifiers"`
+}
+
+// SetACL sets the specified Access Control List for the specified Table
+func (client Client) SetACL(ctx context.Context, accountName, tableName string, acls []SignedIdentifier) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("tables.Client", "SetACL", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("tables.Client", "SetACL", "`tableName` cannot be an empty string.")
+ }
+
+ req, err := client.SetACLPreparer(ctx, accountName, tableName, acls)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "SetACL", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.SetACLSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "tables.Client", "SetACL", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.SetACLResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "SetACL", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// SetACLPreparer prepares the SetACL request.
+func (client Client) SetACLPreparer(ctx context.Context, accountName, tableName string, acls []SignedIdentifier) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ }
+
+ queryParameters := map[string]interface{}{
+ "comp": autorest.Encode("query", "acl"),
+ }
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ }
+
+ input := setAcl{
+ SignedIdentifiers: acls,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/xml; charset=utf-8"),
+ autorest.AsPut(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/{tableName}", pathParameters),
+ autorest.WithQueryParameters(queryParameters),
+ autorest.WithHeaders(headers),
+ autorest.WithXML(&input))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// SetACLSender sends the SetACL request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) SetACLSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// SetACLResponder handles the response to the SetACL request. The method always
+// closes the http.Response Body.
+func (client Client) SetACLResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/table/tables/client.go b/storage/2018-11-09/table/tables/client.go
new file mode 100644
index 0000000..56724b9
--- /dev/null
+++ b/storage/2018-11-09/table/tables/client.go
@@ -0,0 +1,25 @@
+package tables
+
+import (
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// Client is the base client for Table Storage Shares.
+type Client struct {
+ autorest.Client
+ BaseURI string
+}
+
+// New creates an instance of the Client client.
+func New() Client {
+ return NewWithEnvironment(azure.PublicCloud)
+}
+
+// NewWithEnvironment creates an instance of the Client client.
+func NewWithEnvironment(environment azure.Environment) Client {
+ return Client{
+ Client: autorest.NewClientWithUserAgent(UserAgent()),
+ BaseURI: environment.StorageEndpointSuffix,
+ }
+}
diff --git a/storage/2018-11-09/table/tables/create.go b/storage/2018-11-09/table/tables/create.go
new file mode 100644
index 0000000..561f574
--- /dev/null
+++ b/storage/2018-11-09/table/tables/create.go
@@ -0,0 +1,90 @@
+package tables
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type createTableRequest struct {
+ TableName string `json:"TableName"`
+}
+
+// Create creates a new table in the storage account.
+func (client Client) Create(ctx context.Context, accountName, tableName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("tables.Client", "Create", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("tables.Client", "Create", "`tableName` cannot be an empty string.")
+ }
+
+ req, err := client.CreatePreparer(ctx, accountName, tableName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Create", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.CreateSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "tables.Client", "Create", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.CreateResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Create", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// CreatePreparer prepares the Create request.
+func (client Client) CreatePreparer(ctx context.Context, accountName, tableName string) (*http.Request, error) {
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ // NOTE: we could support returning metadata here, but it doesn't appear to be directly useful
+ // vs making a request using the Get methods as-necessary?
+ "Accept": "application/json;odata=nometadata",
+ "Prefer": "return-no-content",
+ }
+
+ body := createTableRequest{
+ TableName: tableName,
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsContentType("application/json"),
+ autorest.AsPost(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPath("/Tables"),
+ autorest.WithJSON(body),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// CreateSender sends the Create request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) CreateSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// CreateResponder handles the response to the Create request. The method always
+// closes the http.Response Body.
+func (client Client) CreateResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/table/tables/delete.go b/storage/2018-11-09/table/tables/delete.go
new file mode 100644
index 0000000..5b5ec86
--- /dev/null
+++ b/storage/2018-11-09/table/tables/delete.go
@@ -0,0 +1,79 @@
+package tables
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Delete deletes the specified table and any data it contains.
+func (client Client) Delete(ctx context.Context, accountName, tableName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("tables.Client", "Delete", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("tables.Client", "Delete", "`tableName` cannot be an empty string.")
+ }
+
+ req, err := client.DeletePreparer(ctx, accountName, tableName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Delete", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.DeleteSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "tables.Client", "Delete", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.DeleteResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Delete", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// DeletePreparer prepares the Delete request.
+func (client Client) DeletePreparer(ctx context.Context, accountName, tableName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ }
+
+ // NOTE: whilst the API documentation says that API Version is Optional
+ // apparently specifying it causes an "invalid content type" to always be returned
+ // as such we omit it here :shrug:
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsDelete(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/Tables('{tableName}')", pathParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// DeleteSender sends the Delete request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) DeleteSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// DeleteResponder handles the response to the Delete request. The method always
+// closes the http.Response Body.
+func (client Client) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusNoContent),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/table/tables/exists.go b/storage/2018-11-09/table/tables/exists.go
new file mode 100644
index 0000000..b3a2718
--- /dev/null
+++ b/storage/2018-11-09/table/tables/exists.go
@@ -0,0 +1,80 @@
+package tables
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// Exists checks that the specified table exists
+func (client Client) Exists(ctx context.Context, accountName, tableName string) (result autorest.Response, err error) {
+ if accountName == "" {
+ return result, validation.NewError("tables.Client", "Exists", "`accountName` cannot be an empty string.")
+ }
+ if tableName == "" {
+ return result, validation.NewError("tables.Client", "Exists", "`tableName` cannot be an empty string.")
+ }
+
+ req, err := client.ExistsPreparer(ctx, accountName, tableName)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Exists", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.ExistsSender(req)
+ if err != nil {
+ result = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "tables.Client", "Exists", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.ExistsResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Exists", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// ExistsPreparer prepares the Exists request.
+func (client Client) ExistsPreparer(ctx context.Context, accountName, tableName string) (*http.Request, error) {
+ pathParameters := map[string]interface{}{
+ "tableName": autorest.Encode("path", tableName),
+ }
+
+ // NOTE: whilst the API documentation says that API Version is Optional
+ // apparently specifying it causes an "invalid content type" to always be returned
+ // as such we omit it here :shrug:
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.AsContentType("application/xml"),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPathParameters("/Tables('{tableName}')", pathParameters))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// ExistsSender sends the Exists request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) ExistsSender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// ExistsResponder handles the response to the Exists request. The method always
+// closes the http.Response Body.
+func (client Client) ExistsResponder(resp *http.Response) (result autorest.Response, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByClosing())
+ result = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/table/tables/lifecycle_test.go b/storage/2018-11-09/table/tables/lifecycle_test.go
new file mode 100644
index 0000000..74ab0fe
--- /dev/null
+++ b/storage/2018-11-09/table/tables/lifecycle_test.go
@@ -0,0 +1,112 @@
+package tables
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "testing"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/tombuildsstuff/giovanni/storage/internal/auth"
+ "github.com/tombuildsstuff/giovanni/testhelpers"
+)
+
+func TestTablesLifecycle(t *testing.T) {
+ client, err := testhelpers.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.TODO()
+ resourceGroup := fmt.Sprintf("acctestrg-%d", testhelpers.RandomInt())
+ accountName := fmt.Sprintf("acctestsa%s", testhelpers.RandomString())
+ tableName := fmt.Sprintf("table%d", testhelpers.RandomInt())
+
+ testData, err := client.BuildTestResources(ctx, resourceGroup, accountName, storage.Storage)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.DestroyTestResources(ctx, resourceGroup, accountName)
+
+ storageAuth := auth.NewSharedKeyLiteTableAuthorizer(accountName, testData.StorageAccountKey)
+ tablesClient := NewWithEnvironment(client.Environment)
+ tablesClient.Client = client.PrepareWithAuthorizer(tablesClient.Client, storageAuth)
+
+ t.Logf("[DEBUG] Creating Table..")
+ if _, err := tablesClient.Create(ctx, accountName, tableName); err != nil {
+ t.Fatalf("Error creating Table %q: %s", tableName, err)
+ }
+
+ // first look it up directly and confirm it's there
+ t.Logf("[DEBUG] Checking if Table exists..")
+ if _, err := tablesClient.Exists(ctx, accountName, tableName); err != nil {
+ t.Fatalf("Error checking if Table %q exists: %s", tableName, err)
+ }
+
+ // then confirm it exists in the Query too
+ t.Logf("[DEBUG] Querying for Tables..")
+ result, err := tablesClient.Query(ctx, accountName, NoMetaData)
+ if err != nil {
+ t.Fatalf("Error retrieving Tables: %s", err)
+ }
+ found := false
+ for _, v := range result.Tables {
+ log.Printf("[DEBUG] Table: %q", v.TableName)
+
+ if v.TableName == tableName {
+ found = true
+ }
+ }
+ if !found {
+ t.Fatalf("%q was not found in the Query response!", tableName)
+ }
+
+ t.Logf("[DEBUG] Setting ACL's for Table %q..", tableName)
+ acls := []SignedIdentifier{
+ {
+ Id: "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=",
+ AccessPolicy: AccessPolicy{
+ Permission: "raud",
+ Start: "2020-11-26T08:49:37.0000000Z",
+ Expiry: "2020-11-27T08:49:37.0000000Z",
+ },
+ },
+ }
+ if _, err := tablesClient.SetACL(ctx, accountName, tableName, acls); err != nil {
+ t.Fatalf("Error setting ACLs: %s", err)
+ }
+
+ t.Logf("[DEBUG] Retrieving ACL's for Table %q..", tableName)
+ retrievedACLs, err := tablesClient.GetACL(ctx, accountName, tableName)
+ if err != nil {
+ t.Fatalf("Error retrieving ACLs: %s", err)
+ }
+
+ if len(retrievedACLs.SignedIdentifiers) != len(acls) {
+ t.Fatalf("Expected %d but got %q ACLs", len(retrievedACLs.SignedIdentifiers), len(acls))
+ }
+
+ for i, retrievedAcl := range retrievedACLs.SignedIdentifiers {
+ expectedAcl := acls[i]
+
+ if retrievedAcl.Id != expectedAcl.Id {
+ t.Fatalf("Expected ID to be %q but got %q", retrievedAcl.Id, expectedAcl.Id)
+ }
+
+ if retrievedAcl.AccessPolicy.Start != expectedAcl.AccessPolicy.Start {
+ t.Fatalf("Expected Start to be %q but got %q", retrievedAcl.AccessPolicy.Start, expectedAcl.AccessPolicy.Start)
+ }
+
+ if retrievedAcl.AccessPolicy.Expiry != expectedAcl.AccessPolicy.Expiry {
+ t.Fatalf("Expected Expiry to be %q but got %q", retrievedAcl.AccessPolicy.Expiry, expectedAcl.AccessPolicy.Expiry)
+ }
+
+ if retrievedAcl.AccessPolicy.Permission != expectedAcl.AccessPolicy.Permission {
+ t.Fatalf("Expected Permission to be %q but got %q", retrievedAcl.AccessPolicy.Permission, expectedAcl.AccessPolicy.Permission)
+ }
+ }
+
+ t.Logf("[DEBUG] Deleting Table %q..", tableName)
+ if _, err := tablesClient.Delete(ctx, accountName, tableName); err != nil {
+ t.Fatalf("Error deleting %q: %s", tableName, err)
+ }
+}
diff --git a/storage/2018-11-09/table/tables/models.go b/storage/2018-11-09/table/tables/models.go
new file mode 100644
index 0000000..d7c382a
--- /dev/null
+++ b/storage/2018-11-09/table/tables/models.go
@@ -0,0 +1,29 @@
+package tables
+
+type MetaDataLevel string
+
+var (
+ NoMetaData MetaDataLevel = "nometadata"
+ MinimalMetaData MetaDataLevel = "minimalmetadata"
+ FullMetaData MetaDataLevel = "fullmetadata"
+)
+
+type GetResultItem struct {
+ TableName string `json:"TableName"`
+
+ // Optional, depending on the MetaData Level
+ ODataType string `json:"odata.type,omitempty"`
+ ODataID string `json:"odata.id,omitEmpty"`
+ ODataEditLink string `json:"odata.editLink,omitEmpty"`
+}
+
+type SignedIdentifier struct {
+ Id string `xml:"Id"`
+ AccessPolicy AccessPolicy `xml:"AccessPolicy"`
+}
+
+type AccessPolicy struct {
+ Start string `xml:"Start"`
+ Expiry string `xml:"Expiry"`
+ Permission string `xml:"Permission"`
+}
diff --git a/storage/2018-11-09/table/tables/query.go b/storage/2018-11-09/table/tables/query.go
new file mode 100644
index 0000000..475370f
--- /dev/null
+++ b/storage/2018-11-09/table/tables/query.go
@@ -0,0 +1,87 @@
+package tables
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/validation"
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+type GetResult struct {
+ autorest.Response
+
+ MetaData string `json:"odata.metadata,omitempty"`
+ Tables []GetResultItem `json:"value"`
+}
+
+// Query returns a list of tables under the specified account.
+func (client Client) Query(ctx context.Context, accountName string, metaDataLevel MetaDataLevel) (result GetResult, err error) {
+ if accountName == "" {
+ return result, validation.NewError("tables.Client", "Query", "`accountName` cannot be an empty string.")
+ }
+
+ req, err := client.QueryPreparer(ctx, accountName, metaDataLevel)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Query", nil, "Failure preparing request")
+ return
+ }
+
+ resp, err := client.QuerySender(req)
+ if err != nil {
+ result.Response = autorest.Response{Response: resp}
+ err = autorest.NewErrorWithError(err, "tables.Client", "Query", resp, "Failure sending request")
+ return
+ }
+
+ result, err = client.QueryResponder(resp)
+ if err != nil {
+ err = autorest.NewErrorWithError(err, "tables.Client", "Query", resp, "Failure responding to request")
+ return
+ }
+
+ return
+}
+
+// QueryPreparer prepares the Query request.
+func (client Client) QueryPreparer(ctx context.Context, accountName string, metaDataLevel MetaDataLevel) (*http.Request, error) {
+ // NOTE: whilst this supports ContinuationTokens and 'Top'
+ // it appears that 'Skip' returns a '501 Not Implemented'
+ // as such, we intentionally don't support those right now
+
+ headers := map[string]interface{}{
+ "x-ms-version": APIVersion,
+ "Accept": fmt.Sprintf("application/json;odata=%s", metaDataLevel),
+ }
+
+ preparer := autorest.CreatePreparer(
+ autorest.AsGet(),
+ autorest.WithBaseURL(endpoints.GetTableEndpoint(client.BaseURI, accountName)),
+ autorest.WithPath("/Tables"),
+ autorest.WithHeaders(headers))
+ return preparer.Prepare((&http.Request{}).WithContext(ctx))
+}
+
+// QuerySender sends the Query request. The method will close the
+// http.Response Body if it receives an error.
+func (client Client) QuerySender(req *http.Request) (*http.Response, error) {
+ return autorest.SendWithSender(client, req,
+ azure.DoRetryWithRegistration(client.Client))
+}
+
+// QueryResponder handles the response to the Query request. The method always
+// closes the http.Response Body.
+func (client Client) QueryResponder(resp *http.Response) (result GetResult, err error) {
+ err = autorest.Respond(
+ resp,
+ client.ByInspecting(),
+ azure.WithErrorUnlessStatusCode(http.StatusOK),
+ autorest.ByUnmarshallingJSON(&result),
+ autorest.ByClosing())
+ result.Response = autorest.Response{Response: resp}
+
+ return
+}
diff --git a/storage/2018-11-09/table/tables/resource_id.go b/storage/2018-11-09/table/tables/resource_id.go
new file mode 100644
index 0000000..1052317
--- /dev/null
+++ b/storage/2018-11-09/table/tables/resource_id.go
@@ -0,0 +1,54 @@
+package tables
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/tombuildsstuff/giovanni/storage/internal/endpoints"
+)
+
+// GetResourceID returns the Resource ID for the given Table
+// This can be useful when, for example, you're using this as a unique identifier
+func (client Client) GetResourceID(accountName, tableName string) string {
+ domain := endpoints.GetTableEndpoint(client.BaseURI, accountName)
+ return fmt.Sprintf("%s/Tables('%s')", domain, tableName)
+}
+
+type ResourceID struct {
+ AccountName string
+ TableName string
+}
+
+// ParseResourceID parses the Resource ID and returns an object which
+// can be used to interact with the Table within the specified Storage Account
+func (client Client) ParseResourceID(id string) (*ResourceID, error) {
+ // example: https://foo.table.core.windows.net/Table('foo')
+ if id == "" {
+ return nil, fmt.Errorf("`id` was empty")
+ }
+
+ uri, err := url.Parse(id)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing ID as a URL: %s", err)
+ }
+
+ accountName, err := endpoints.GetAccountNameFromEndpoint(uri.Host)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing Account Name: %s", err)
+ }
+
+ // assume there a `Table('')`
+ path := strings.TrimPrefix(uri.Path, "/")
+ if !strings.HasPrefix(path, "Tables('") || !strings.HasSuffix(path, "')") {
+ return nil, fmt.Errorf("Expected the Table Name to be in the format `Tables('name')` but got %q", path)
+ }
+
+ // strip off the `Table('')`
+ tableName := strings.TrimPrefix(uri.Path, "/Tables('")
+ tableName = strings.TrimSuffix(tableName, "')")
+ return &ResourceID{
+ AccountName: *accountName,
+ TableName: tableName,
+ }, nil
+}
diff --git a/storage/2018-11-09/table/tables/resource_id_test.go b/storage/2018-11-09/table/tables/resource_id_test.go
new file mode 100644
index 0000000..5557f81
--- /dev/null
+++ b/storage/2018-11-09/table/tables/resource_id_test.go
@@ -0,0 +1,78 @@
+package tables
+
+import (
+ "testing"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+func TestGetResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Expected string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Expected: "https://account1.table.core.chinacloudapi.cn/Tables('table1')",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Expected: "https://account1.table.core.cloudapi.de/Tables('table1')",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Expected: "https://account1.table.core.windows.net/Tables('table1')",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Expected: "https://account1.table.core.usgovcloudapi.net/Tables('table1')",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual := c.GetResourceID("account1", "table1")
+ if actual != v.Expected {
+ t.Fatalf("Expected the Resource ID to be %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestParseResourceID(t *testing.T) {
+ testData := []struct {
+ Environment azure.Environment
+ Input string
+ }{
+ {
+ Environment: azure.ChinaCloud,
+ Input: "https://account1.table.core.chinacloudapi.cn/Tables('table1')",
+ },
+ {
+ Environment: azure.GermanCloud,
+ Input: "https://account1.table.core.cloudapi.de/Tables('table1')",
+ },
+ {
+ Environment: azure.PublicCloud,
+ Input: "https://account1.table.core.windows.net/Tables('table1')",
+ },
+ {
+ Environment: azure.USGovernmentCloud,
+ Input: "https://account1.table.core.usgovcloudapi.net/Tables('table1')",
+ },
+ }
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing Environment %q", v.Environment.Name)
+ c := NewWithEnvironment(v.Environment)
+ actual, err := c.ParseResourceID(v.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual.AccountName != "account1" {
+ t.Fatalf("Expected Account Name to be `account1` but got %q", actual.AccountName)
+ }
+ if actual.TableName != "table1" {
+ t.Fatalf("Expected Table Name to be `table1` but got %q", actual.TableName)
+ }
+ }
+}
diff --git a/storage/2018-11-09/table/tables/version.go b/storage/2018-11-09/table/tables/version.go
new file mode 100644
index 0000000..c682db5
--- /dev/null
+++ b/storage/2018-11-09/table/tables/version.go
@@ -0,0 +1,14 @@
+package tables
+
+import (
+ "fmt"
+
+ "github.com/tombuildsstuff/giovanni/version"
+)
+
+// APIVersion is the version of the API used for all Storage API Operations
+const APIVersion = "2018-11-09"
+
+func UserAgent() string {
+ return fmt.Sprintf("tombuildsstuff/giovanni/%s storage/%s", version.Number, APIVersion)
+}
diff --git a/storage/internal/auth/TODO.md b/storage/internal/auth/TODO.md
new file mode 100644
index 0000000..e514d2d
--- /dev/null
+++ b/storage/internal/auth/TODO.md
@@ -0,0 +1 @@
+TODO: this can be removed once https://github.com/Azure/go-autorest/pull/416 has been merged
\ No newline at end of file
diff --git a/storage/internal/auth/authorizer_shared_key_lite.go b/storage/internal/auth/authorizer_shared_key_lite.go
new file mode 100644
index 0000000..012441e
--- /dev/null
+++ b/storage/internal/auth/authorizer_shared_key_lite.go
@@ -0,0 +1,87 @@
+package auth
+
+import (
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+)
+
+// SharedKeyLiteAuthorizer implements an authorization for Shared Key Lite
+// this can be used for interaction with Blob, File and Queue Storage Endpoints
+type SharedKeyLiteAuthorizer struct {
+ storageAccountName string
+ storageAccountKey string
+}
+
+// NewSharedKeyLiteAuthorizer crates a SharedKeyLiteAuthorizer using the given credentials
+func NewSharedKeyLiteAuthorizer(accountName, accountKey string) *SharedKeyLiteAuthorizer {
+ return &SharedKeyLiteAuthorizer{
+ storageAccountName: accountName,
+ storageAccountKey: accountKey,
+ }
+}
+
+// WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose
+// value is "SharedKeyLite " followed by the computed key.
+// This can be used for the Blob, Queue, and File Services
+//
+// from: https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
+// You may use Shared Key Lite authorization to authorize a request made against the
+// 2009-09-19 version and later of the Blob and Queue services,
+// and version 2014-02-14 and later of the File services.
+func (skl *SharedKeyLiteAuthorizer) WithAuthorization() autorest.PrepareDecorator {
+ return func(p autorest.Preparer) autorest.Preparer {
+ return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
+ r, err := p.Prepare(r)
+ if err != nil {
+ return r, err
+ }
+
+ key, err := buildSharedKeyLite(skl.storageAccountName, skl.storageAccountKey, r)
+ if err != nil {
+ return r, err
+ }
+
+ sharedKeyHeader := formatSharedKeyLiteAuthorizationHeader(skl.storageAccountName, *key)
+ return autorest.Prepare(r, autorest.WithHeader(HeaderAuthorization, sharedKeyHeader))
+ })
+ }
+}
+func buildSharedKeyLite(accountName, storageAccountKey string, r *http.Request) (*string, error) {
+ // first ensure the relevant headers are configured
+ prepareHeadersForRequest(r)
+
+ sharedKey, err := computeSharedKeyLite(r.Method, r.URL.String(), accountName, r.Header)
+ if err != nil {
+ return nil, err
+ }
+
+ // we then need to HMAC that value
+ hmacdValue := hmacValue(storageAccountKey, *sharedKey)
+ return &hmacdValue, nil
+}
+
+// computeSharedKeyLite computes the Shared Key Lite required for Storage Authentication
+// NOTE: this function assumes that the `x-ms-date` field is set
+func computeSharedKeyLite(verb, url string, accountName string, headers http.Header) (*string, error) {
+ canonicalizedResource, err := buildCanonicalizedResource(url, accountName)
+ if err != nil {
+ return nil, err
+ }
+
+ canonicalizedHeaders := buildCanonicalizedHeader(headers)
+ canonicalizedString := buildCanonicalizedStringForSharedKeyLite(verb, headers, canonicalizedHeaders, *canonicalizedResource)
+ return &canonicalizedString, nil
+}
+
+func buildCanonicalizedStringForSharedKeyLite(verb string, headers http.Header, canonicalizedHeaders, canonicalizedResource string) string {
+ return strings.Join([]string{
+ verb,
+ headers.Get(HeaderContentMD5), // TODO: this appears to always be empty?
+ headers.Get(HeaderContentType),
+ "", // date should be nil, apparently :shrug:
+ canonicalizedHeaders,
+ canonicalizedResource,
+ }, "\n")
+}
diff --git a/storage/internal/auth/authorizer_shared_key_lite_table.go b/storage/internal/auth/authorizer_shared_key_lite_table.go
new file mode 100644
index 0000000..67296d0
--- /dev/null
+++ b/storage/internal/auth/authorizer_shared_key_lite_table.go
@@ -0,0 +1,84 @@
+package auth
+
+import (
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+)
+
+// SharedKeyLiteTableAuthorizer implements an authorization for Shared Key Lite
+// this can be used for interaction with Table Storage Endpoints
+type SharedKeyLiteTableAuthorizer struct {
+ storageAccountName string
+ storageAccountKey string
+}
+
+// NewSharedKeyLiteAuthorizer crates a SharedKeyLiteAuthorizer using the given credentials
+func NewSharedKeyLiteTableAuthorizer(accountName, accountKey string) *SharedKeyLiteTableAuthorizer {
+ return &SharedKeyLiteTableAuthorizer{
+ storageAccountName: accountName,
+ storageAccountKey: accountKey,
+ }
+}
+
+// WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose
+// value is "SharedKeyLite " followed by the computed key.
+// This can be used for the Blob, Queue, and File Services
+//
+// from: https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
+// You may use Shared Key Lite authorization to authorize a request made against the
+// 2009-09-19 version and later of the Blob and Queue services,
+// and version 2014-02-14 and later of the File services.
+func (skl *SharedKeyLiteTableAuthorizer) WithAuthorization() autorest.PrepareDecorator {
+ return func(p autorest.Preparer) autorest.Preparer {
+ return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
+ r, err := p.Prepare(r)
+ if err != nil {
+ return r, err
+ }
+
+ key, err := buildSharedKeyLiteTable(skl.storageAccountName, skl.storageAccountKey, r)
+ if err != nil {
+ return r, err
+ }
+
+ sharedKeyHeader := formatSharedKeyLiteAuthorizationHeader(skl.storageAccountName, *key)
+ return autorest.Prepare(r, autorest.WithHeader(HeaderAuthorization, sharedKeyHeader))
+ })
+ }
+}
+
+func buildSharedKeyLiteTable(accountName, storageAccountKey string, r *http.Request) (*string, error) {
+ // first ensure the relevant headers are configured
+ prepareHeadersForRequest(r)
+
+ sharedKey, err := computeSharedKeyLiteTable(r.URL.String(), accountName, r.Header)
+ if err != nil {
+ return nil, err
+ }
+
+ // we then need to HMAC that value
+ hmacdValue := hmacValue(storageAccountKey, *sharedKey)
+ return &hmacdValue, nil
+}
+
+// computeSharedKeyLite computes the Shared Key Lite required for Storage Authentication
+// NOTE: this function assumes that the `x-ms-date` field is set
+func computeSharedKeyLiteTable(url string, accountName string, headers http.Header) (*string, error) {
+ dateHeader := headers.Get("x-ms-date")
+ canonicalizedResource, err := buildCanonicalizedResource(url, accountName)
+ if err != nil {
+ return nil, err
+ }
+
+ canonicalizedString := buildCanonicalizedStringForSharedKeyLiteTable(*canonicalizedResource, dateHeader)
+ return &canonicalizedString, nil
+}
+
+func buildCanonicalizedStringForSharedKeyLiteTable(canonicalizedResource, dateHeader string) string {
+ return strings.Join([]string{
+ dateHeader,
+ canonicalizedResource,
+ }, "\n")
+}
diff --git a/storage/internal/auth/authorizer_shared_key_lite_test.go b/storage/internal/auth/authorizer_shared_key_lite_test.go
new file mode 100644
index 0000000..54e6a88
--- /dev/null
+++ b/storage/internal/auth/authorizer_shared_key_lite_test.go
@@ -0,0 +1,36 @@
+package auth
+
+import (
+ "testing"
+)
+
+func TestBuildCanonicalizedStringForSharedKeyLite(t *testing.T) {
+ testData := []struct {
+ name string
+ headers map[string][]string
+ canonicalizedHeaders string
+ canonicalizedResource string
+ verb string
+ expected string
+ }{
+ {
+ name: "completed",
+ verb: "NOM",
+ headers: map[string][]string{
+ "Content-MD5": {"abc123"},
+ "Content-Type": {"vnd/panda-pops+v1"},
+ },
+ canonicalizedHeaders: "all-the-headers",
+ canonicalizedResource: "all-the-resources",
+ expected: "NOM\n\nvnd/panda-pops+v1\n\nall-the-headers\nall-the-resources",
+ },
+ }
+
+ for _, test := range testData {
+ t.Logf("Test: %q", test.name)
+ actual := buildCanonicalizedStringForSharedKeyLite(test.verb, test.headers, test.canonicalizedHeaders, test.canonicalizedResource)
+ if actual != test.expected {
+ t.Fatalf("Expected %q but got %q", test.expected, actual)
+ }
+ }
+}
diff --git a/storage/internal/auth/consts.go b/storage/internal/auth/consts.go
new file mode 100644
index 0000000..612be83
--- /dev/null
+++ b/storage/internal/auth/consts.go
@@ -0,0 +1,19 @@
+package auth
+
+var (
+ HeaderAuthorization = "Authorization"
+ HeaderContentLength = "Content-Length"
+ HeaderContentEncoding = "Content-Encoding"
+ HeaderContentLanguage = "Content-Language"
+ HeaderContentType = "Content-Type"
+ HeaderContentMD5 = "Content-MD5"
+ HeaderIfModifiedSince = "If-Modified-Since"
+ HeaderIfMatch = "If-Match"
+ HeaderIfNoneMatch = "If-None-Match"
+ HeaderIfUnmodifiedSince = "If-Unmodified-Since"
+ HeaderMSDate = "X-Ms-Date"
+ HeaderRange = "Range"
+
+ StorageEmulatorAccountName = "devstoreaccount1"
+ StorageEmulatorAccountKey = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
+)
diff --git a/storage/internal/auth/helpers.go b/storage/internal/auth/helpers.go
new file mode 100644
index 0000000..19e4c05
--- /dev/null
+++ b/storage/internal/auth/helpers.go
@@ -0,0 +1,122 @@
+package auth
+
+import (
+ "bytes"
+ "crypto/hmac"
+ "crypto/sha256"
+ "encoding/base64"
+ "fmt"
+ "net/http"
+ "net/url"
+ "sort"
+ "strings"
+ "time"
+)
+
+// NOTE: this all came from the Azure SDK for Go, but has been refactored to aid creating a Preparer.
+
+// buildCanonicalizedHeader builds the Canonicalized Header required to sign Storage Requests
+func buildCanonicalizedHeader(headers http.Header) string {
+ cm := make(map[string]string)
+
+ for k, v := range headers {
+ headerName := strings.TrimSpace(strings.ToLower(k))
+ if strings.HasPrefix(headerName, "x-ms-") {
+ cm[headerName] = v[0]
+ }
+ }
+
+ if len(cm) == 0 {
+ return ""
+ }
+
+ var keys []string
+ for key := range cm {
+ keys = append(keys, key)
+ }
+
+ sort.Strings(keys)
+
+ ch := bytes.NewBufferString("")
+
+ for _, key := range keys {
+ ch.WriteString(key)
+ ch.WriteRune(':')
+ ch.WriteString(cm[key])
+ ch.WriteRune('\n')
+ }
+
+ return strings.TrimSuffix(string(ch.Bytes()), "\n")
+}
+
+// buildCanonicalizedResource builds the Canonical Resource required for to sign Storage Account requests
+func buildCanonicalizedResource(uri, accountName string) (*string, error) {
+ u, err := url.Parse(uri)
+ if err != nil {
+ return nil, err
+ }
+
+ cr := bytes.NewBufferString("")
+ if accountName != StorageEmulatorAccountName {
+ cr.WriteString("/")
+ cr.WriteString(primaryStorageAccountName(accountName))
+ }
+
+ if len(u.Path) > 0 {
+ // Any portion of the CanonicalizedResource string that is derived from
+ // the resource's URI should be encoded exactly as it is in the URI.
+ // -- https://msdn.microsoft.com/en-gb/library/azure/dd179428.aspx
+ cr.WriteString(u.EscapedPath())
+ }
+
+ // TODO: replace this with less of a hack
+ if comp := u.Query().Get("comp"); comp != "" {
+ cr.WriteString(fmt.Sprintf("?comp=%s", comp))
+ }
+
+ out := string(cr.Bytes())
+ return &out, nil
+}
+
+func formatSharedKeyLiteAuthorizationHeader(accountName, key string) string {
+ canonicalizedAccountName := primaryStorageAccountName(accountName)
+ return fmt.Sprintf("SharedKeyLite %s:%s", canonicalizedAccountName, key)
+}
+
+// hmacValue base-64 decodes the storageAccountKey, then signs the string with it
+// as outlined here: https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
+func hmacValue(storageAccountKey, canonicalizedString string) string {
+ key, err := base64.StdEncoding.DecodeString(storageAccountKey)
+ if err != nil {
+ return ""
+ }
+
+ encr := hmac.New(sha256.New, []byte(key))
+ encr.Write([]byte(canonicalizedString))
+ return base64.StdEncoding.EncodeToString(encr.Sum(nil))
+}
+
+// prepareHeadersForRequest prepares a request so that it can be signed
+// by ensuring the `date` and `x-ms-date` headers are set
+func prepareHeadersForRequest(r *http.Request) {
+ if r.Header == nil {
+ r.Header = http.Header{}
+ }
+
+ date := time.Now().UTC().Format(http.TimeFormat)
+
+ // a date must be set, X-Ms-Date should be used when both are set; but let's set both for completeness
+ r.Header.Set("date", date)
+ r.Header.Set("x-ms-date", date)
+}
+
+// primaryStorageAccountName returns the name of the primary for a given Storage Account
+func primaryStorageAccountName(input string) string {
+ // from https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
+ // If you are accessing the secondary location in a storage account for which
+ // read-access geo-replication (RA-GRS) is enabled, do not include the
+ // -secondary designation in the authorization header.
+ // For authorization purposes, the account name is always the name of the primary location,
+ // even for secondary access.
+ return strings.TrimSuffix(input, "-secondary")
+}
diff --git a/storage/internal/auth/helpers_test.go b/storage/internal/auth/helpers_test.go
new file mode 100644
index 0000000..4354f26
--- /dev/null
+++ b/storage/internal/auth/helpers_test.go
@@ -0,0 +1,271 @@
+package auth
+
+import (
+ "encoding/base64"
+ "net/http"
+ "testing"
+)
+
+func TestBuildCanonicalizedHeader(t *testing.T) {
+ testData := []struct {
+ Input http.Header
+ Expected string
+ }{
+ {
+ // no headers
+ Expected: "",
+ Input: map[string][]string{
+ "": {""},
+ },
+ },
+ {
+ // no x-ms headers
+ Expected: "",
+ Input: map[string][]string{
+ "panda": {"pops"},
+ },
+ },
+ {
+ // only a single x-ms header
+ Expected: "x-ms-panda:nom",
+ Input: map[string][]string{
+ "x-ms-panda": {"nom"},
+ },
+ },
+ {
+ // multiple x-ms headers
+ Expected: "x-ms-panda:nom\nx-ms-tiger:rawr",
+ Input: map[string][]string{
+ "x-ms-panda": {"nom"},
+ "x-ms-tiger": {"rawr"},
+ },
+ },
+ {
+ // multiple x-ms headers, out of order
+ Expected: "x-ms-panda:nom\nx-ms-tiger:rawr",
+ Input: map[string][]string{
+ "x-ms-tiger": {"rawr"},
+ "x-ms-panda": {"nom"},
+ },
+ },
+ {
+ // mixed headers (some ms, some non-ms)
+ Expected: "x-ms-panda:nom\nx-ms-tiger:rawr",
+ Input: map[string][]string{
+ "x-ms-tiger": {"rawr"},
+ "panda": {"pops"},
+ "x-ms-panda": {"nom"},
+ },
+ },
+ {
+ // casing
+ Expected: "x-ms-panda:nom\nx-ms-tiger:rawr",
+ Input: map[string][]string{
+ "X-Ms-Tiger": {"rawr"},
+ "X-Ms-Panda": {"nom"},
+ },
+ },
+ }
+
+ for _, v := range testData {
+ actual := buildCanonicalizedHeader(v.Input)
+ if actual != v.Expected {
+ t.Fatalf("Expected %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestBuildCanonicalizedResource(t *testing.T) {
+ testData := []struct {
+ name string
+ accountName string
+ uri string
+ expected string
+ expectError bool
+ }{
+ {
+ name: "invalid uri",
+ accountName: "example",
+ uri: "://example.com",
+ expected: "",
+ expectError: true,
+ },
+ {
+ name: "storage emulator doesn't get prefix",
+ accountName: StorageEmulatorAccountName,
+ uri: "http://www.example.com/foo",
+ expected: "/foo",
+ },
+ {
+ name: "non storage emulator gets prefix",
+ accountName: StorageEmulatorAccountName + "test",
+ uri: "http://www.example.com/foo",
+ expected: "/" + StorageEmulatorAccountName + "test/foo",
+ },
+ {
+ name: "uri encoding",
+ accountName: "example",
+ uri: "",
+ expected: "/example%3Chello%3E",
+ },
+ {
+ name: "comp-arg",
+ accountName: "example",
+ uri: "/endpoint?first=true&comp=bar&second=false&third=panda",
+ expected: "/example/endpoint?comp=bar",
+ },
+ {
+ name: "arguments",
+ accountName: "example",
+ uri: "/endpoint?first=true&second=false&third=panda",
+ expected: "/example/endpoint",
+ },
+ }
+
+ for _, test := range testData {
+ t.Logf("Test %q", test.name)
+ actual, err := buildCanonicalizedResource(test.uri, test.accountName)
+ if err != nil {
+ if test.expectError {
+ continue
+ }
+
+ t.Fatalf("Error: %s", err)
+ }
+
+ if *actual != test.expected {
+ t.Fatalf("Expected %q but got %q", test.expected, *actual)
+ }
+ }
+}
+
+func TestFormatSharedKeyLiteAuthorizationHeader(t *testing.T) {
+ testData := []struct {
+ name string
+ accountName string
+ accountKey string
+ expected string
+ }{
+ {
+ name: "primary",
+ accountName: "account1",
+ accountKey: "examplekey",
+ expected: "SharedKeyLite account1:examplekey",
+ },
+ {
+ name: "secondary",
+ accountName: "account1-secondary",
+ accountKey: "examplekey",
+ expected: "SharedKeyLite account1:examplekey",
+ },
+ }
+
+ for _, test := range testData {
+ t.Logf("Test: %q", test.name)
+ actual := formatSharedKeyLiteAuthorizationHeader(test.accountName, test.accountKey)
+
+ if actual != test.expected {
+ t.Fatalf("Expected %q but got %q", test.expected, actual)
+ }
+ }
+}
+
+func TestHMAC(t *testing.T) {
+ testData := []struct {
+ Expected string
+ StorageAccountKey string
+ CanonicalizedString string
+ }{
+ {
+ // When Storage Key isn't base-64 encoded
+ Expected: "",
+ StorageAccountKey: "bar",
+ CanonicalizedString: "foobarzoo",
+ },
+ {
+ // Valid
+ Expected: "h5U0ATVX6SpbFX1H6GNuxIMeXXCILLoIvhflPtuQZ30=",
+ StorageAccountKey: base64.StdEncoding.EncodeToString([]byte("bar")),
+ CanonicalizedString: "foobarzoo",
+ },
+ }
+
+ for _, v := range testData {
+ actual := hmacValue(v.StorageAccountKey, v.CanonicalizedString)
+ if actual != v.Expected {
+ t.Fatalf("Expected %q but got %q", v.Expected, actual)
+ }
+ }
+}
+
+func TestTestPrepareHeadersForRequest(t *testing.T) {
+ request, err := http.NewRequest("GET", "http://example.com", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ headers := []string{
+ "Date",
+ "X-Ms-Date",
+ }
+
+ for _, header := range headers {
+ existingVal := request.Header.Get(header)
+ if existingVal != "" {
+ t.Fatalf("%q had a value prior to being set: %q", header, existingVal)
+ }
+ }
+
+ prepareHeadersForRequest(request)
+
+ for _, header := range headers {
+ updatedVal := request.Header.Get(header)
+ if updatedVal == "" {
+ t.Fatalf("%q didn't have a value after being set: %q", header, updatedVal)
+ }
+ }
+}
+
+func TestPrepareHeadersForRequestWithNoneConfigured(t *testing.T) {
+ request, err := http.NewRequest("GET", "http://example.com", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ request.Header = nil
+ prepareHeadersForRequest(request)
+
+ if request.Header == nil {
+ t.Fatalf("Expected `request.Header` to not be nil, but it was!")
+ }
+}
+
+func TestPrimaryStorageAccountName(t *testing.T) {
+ testData := []struct {
+ Expected string
+ Input string
+ }{
+ {
+ // Empty
+ Expected: "",
+ Input: "",
+ },
+ {
+ // Primary
+ Expected: "bar",
+ Input: "bar",
+ },
+ {
+ // Secondary
+ Expected: "bar",
+ Input: "bar-secondary",
+ },
+ }
+
+ for _, v := range testData {
+ actual := primaryStorageAccountName(v.Input)
+ if actual != v.Expected {
+ t.Fatalf("Expected %q but got %q", v.Expected, actual)
+ }
+ }
+}
diff --git a/storage/internal/endpoints/endpoints.go b/storage/internal/endpoints/endpoints.go
new file mode 100644
index 0000000..e9a18c8
--- /dev/null
+++ b/storage/internal/endpoints/endpoints.go
@@ -0,0 +1,34 @@
+package endpoints
+
+import (
+ "fmt"
+ "strings"
+)
+
+func GetAccountNameFromEndpoint(endpoint string) (*string, error) {
+ segments := strings.Split(endpoint, ".")
+ if len(segments) == 0 {
+ return nil, fmt.Errorf("The Endpoint contained no segments")
+ }
+ return &segments[0], nil
+}
+
+// GetBlobEndpoint returns the endpoint for Blob API Operations on this storage account
+func GetBlobEndpoint(baseUri string, accountName string) string {
+ return fmt.Sprintf("https://%s.blob.%s", accountName, baseUri)
+}
+
+// GetFileEndpoint returns the endpoint for File Share API Operations on this storage account
+func GetFileEndpoint(baseUri string, accountName string) string {
+ return fmt.Sprintf("https://%s.file.%s", accountName, baseUri)
+}
+
+// GetQueueEndpoint returns the endpoint for Queue API Operations on this storage account
+func GetQueueEndpoint(baseUri string, accountName string) string {
+ return fmt.Sprintf("https://%s.queue.%s", accountName, baseUri)
+}
+
+// GetTableEndpoint returns the endpoint for Table API Operations on this storage account
+func GetTableEndpoint(baseUri string, accountName string) string {
+ return fmt.Sprintf("https://%s.table.%s", accountName, baseUri)
+}
diff --git a/storage/internal/helpers/errors.go b/storage/internal/helpers/errors.go
new file mode 100644
index 0000000..9090bf6
--- /dev/null
+++ b/storage/internal/helpers/errors.go
@@ -0,0 +1,107 @@
+package helpers
+
+import (
+ "encoding/json"
+ "encoding/xml"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "strings"
+
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/azure"
+)
+
+// TODO: trial switching over to this, then upstream it
+
+// WithErrorUnlessStatusCode returns a RespondDecorator that emits an
+// azure.RequestError by reading the response body unless the response HTTP status code
+// is among the set passed.
+//
+// If there is a chance service may return responses other than the Azure error
+// format and the response cannot be parsed into an error, a decoding error will
+// be returned containing the response body. In any case, the Responder will
+// return an error if the status code is not satisfied.
+//
+// If this Responder returns an error, the response body will be replaced with
+// an in-memory reader, which needs no further closing.
+func WithErrorUnlessStatusCode(codes ...int) autorest.RespondDecorator {
+ return func(r autorest.Responder) autorest.Responder {
+ return autorest.ResponderFunc(func(resp *http.Response) error {
+ err := r.Respond(resp)
+ if err == nil && !autorest.ResponseHasStatusCode(resp, codes...) {
+ var e azure.RequestError
+ defer resp.Body.Close()
+
+ contentType := autorest.EncodedAsJSON
+ if resp != nil {
+ contentTypeStr := resp.Header.Get("Content-Type")
+ if strings.EqualFold(contentTypeStr, "application/xml") {
+ contentType = autorest.EncodedAsXML
+ }
+ }
+
+ // Copy and replace the Body in case it does not contain an error object.
+ // This will leave the Body available to the caller.
+ b, decodeErr := autorest.CopyAndDecode(contentType, resp.Body, &e)
+ resp.Body = ioutil.NopCloser(&b)
+ if decodeErr != nil {
+ return fmt.Errorf("autorest/azure: error response cannot be parsed: %q error: %v", b.String(), decodeErr)
+ }
+ if e.ServiceError == nil {
+ switch contentType {
+ case autorest.EncodedAsJSON:
+ // Check if error is unwrapped ServiceError
+ if err := json.Unmarshal(b.Bytes(), &e.ServiceError); err != nil {
+ return err
+ }
+ break
+ case autorest.EncodedAsXML:
+ // Check if error is unwrapped ServiceError
+ if err := xml.Unmarshal(b.Bytes(), &e.ServiceError); err != nil {
+ return err
+ }
+ break
+ }
+ }
+ if e.ServiceError.Message == "" {
+
+ rawBody := map[string]interface{}{}
+
+ switch contentType {
+ case autorest.EncodedAsJSON:
+ // if we're here it means the returned error wasn't OData v4 compliant.
+ // try to unmarshal the body as raw JSON in hopes of getting something.
+ if err := json.Unmarshal(b.Bytes(), &rawBody); err != nil {
+ return err
+ }
+ break
+
+ case autorest.EncodedAsXML:
+ // if we're here it means the returned error wasn't OData v4 compliant.
+ // try to unmarshal the body as raw XML in hopes of getting something.
+ if err := xml.Unmarshal(b.Bytes(), &rawBody); err != nil {
+ return err
+ }
+ break
+ }
+
+ e.ServiceError = &azure.ServiceError{
+ Code: "Unknown",
+ Message: "Unknown service error",
+ }
+ if len(rawBody) > 0 {
+ e.ServiceError.Details = []map[string]interface{}{rawBody}
+ }
+ }
+ e.Response = resp
+ e.RequestID = azure.ExtractRequestID(resp)
+ if e.StatusCode == nil {
+ e.StatusCode = resp.StatusCode
+ }
+ err = &e
+ }
+ return err
+ })
+ }
+}
diff --git a/storage/internal/metadata/parse.go b/storage/internal/metadata/parse.go
new file mode 100644
index 0000000..1880d40
--- /dev/null
+++ b/storage/internal/metadata/parse.go
@@ -0,0 +1,22 @@
+package metadata
+
+import (
+ "net/http"
+ "strings"
+)
+
+// ParseFromHeaders parses the meta data from the headers
+func ParseFromHeaders(headers http.Header) map[string]string {
+ metaData := make(map[string]string, 0)
+ for k, v := range headers {
+ key := strings.ToLower(k)
+ prefix := "x-ms-meta-"
+ if !strings.HasPrefix(key, prefix) {
+ continue
+ }
+
+ key = strings.TrimPrefix(key, prefix)
+ metaData[key] = v[0]
+ }
+ return metaData
+}
diff --git a/storage/internal/metadata/set.go b/storage/internal/metadata/set.go
new file mode 100644
index 0000000..d88fbd7
--- /dev/null
+++ b/storage/internal/metadata/set.go
@@ -0,0 +1,13 @@
+package metadata
+
+import "fmt"
+
+// SetIntoHeaders sets the provided MetaData into the headers
+func SetIntoHeaders(headers map[string]interface{}, metaData map[string]string) map[string]interface{} {
+ for k, v := range metaData {
+ key := fmt.Sprintf("x-ms-meta-%s", k)
+ headers[key] = v
+ }
+
+ return headers
+}
diff --git a/storage/internal/metadata/validation.go b/storage/internal/metadata/validation.go
new file mode 100644
index 0000000..1fa1f9a
--- /dev/null
+++ b/storage/internal/metadata/validation.go
@@ -0,0 +1,105 @@
+package metadata
+
+import (
+ "fmt"
+ "regexp"
+ "strings"
+)
+
+var cSharpKeywords = map[string]*struct{}{
+ "abstract": {},
+ "as": {},
+ "base": {},
+ "bool": {},
+ "break": {},
+ "byte": {},
+ "case": {},
+ "catch": {},
+ "char": {},
+ "checked": {},
+ "class": {},
+ "const": {},
+ "continue": {},
+ "decimal": {},
+ "default": {},
+ "delegate": {},
+ "do": {},
+ "double": {},
+ "else": {},
+ "enum": {},
+ "event": {},
+ "explicit": {},
+ "extern": {},
+ "false": {},
+ "finally": {},
+ "fixed": {},
+ "float": {},
+ "for": {},
+ "foreach": {},
+ "goto": {},
+ "if": {},
+ "implicit": {},
+ "in": {},
+ "int": {},
+ "interface": {},
+ "internal": {},
+ "is": {},
+ "lock": {},
+ "long": {},
+ "namespace": {},
+ "new": {},
+ "null": {},
+ "object": {},
+ "operator": {},
+ "out": {},
+ "override": {},
+ "params": {},
+ "private": {},
+ "protected": {},
+ "public": {},
+ "readonly": {},
+ "ref": {},
+ "return": {},
+ "sbyte": {},
+ "sealed": {},
+ "short": {},
+ "sizeof": {},
+ "stackalloc": {},
+ "static": {},
+ "string": {},
+ "struct": {},
+ "switch": {},
+ "this": {},
+ "throw": {},
+ "true": {},
+ "try": {},
+ "typeof": {},
+ "uint": {},
+ "ulong": {},
+ "unchecked": {},
+ "unsafe": {},
+ "ushort": {},
+ "using": {},
+ "void": {},
+ "volatile": {},
+ "while": {},
+}
+
+func Validate(input map[string]string) error {
+
+ for k := range input {
+ isCSharpKeyword := cSharpKeywords[strings.ToLower(k)] != nil
+ if isCSharpKeyword {
+ return fmt.Errorf("%q is not a valid key (C# keyword)", k)
+ }
+
+ // must begin with a letter, underscore
+ // the rest: letters, digits and underscores
+ r, _ := regexp.Compile(`^([A-Za-z_]{1}[A-Za-z0-9_]{1,})$`)
+ if !r.MatchString(k) {
+ return fmt.Errorf("MetaData must start with letters or an underscores. Got %q.", k)
+ }
+ }
+
+ return nil
+}
diff --git a/storage/internal/metadata/validation_test.go b/storage/internal/metadata/validation_test.go
new file mode 100644
index 0000000..bd39192
--- /dev/null
+++ b/storage/internal/metadata/validation_test.go
@@ -0,0 +1,68 @@
+package metadata
+
+import "testing"
+
+func TestValidationCSharpKeywords(t *testing.T) {
+ for key := range cSharpKeywords {
+ t.Logf("[DEBUG] Testing %q", key)
+
+ err := Validate(map[string]string{
+ key: "value",
+ })
+ if err == nil {
+ t.Fatalf("Expected an error but didn't get one for %q", key)
+ }
+ }
+}
+
+func TestValidation(t *testing.T) {
+ testData := []struct {
+ Input string
+ ShouldBeValid bool
+ }{
+ {
+ Input: "",
+ ShouldBeValid: false,
+ },
+ {
+ Input: "abc123",
+ ShouldBeValid: true,
+ },
+ {
+ Input: "_abc123",
+ ShouldBeValid: true,
+ },
+ {
+ Input: "123abc",
+ ShouldBeValid: false,
+ },
+ {
+ Input: "a_123abc",
+ ShouldBeValid: true,
+ },
+ {
+ Input: "abc_123",
+ ShouldBeValid: true,
+ },
+ {
+ Input: "abc123_",
+ ShouldBeValid: true,
+ },
+ {
+ Input: "ABC123",
+ ShouldBeValid: true,
+ },
+ }
+
+ for _, v := range testData {
+ t.Logf("[DEBUG] Testing %q", v.Input)
+
+ err := Validate(map[string]string{
+ v.Input: "value",
+ })
+ actual := err == nil
+ if v.ShouldBeValid != actual {
+ t.Fatalf("Expected %t but got %t for %q", v.ShouldBeValid, actual, v.Input)
+ }
+ }
+}
diff --git a/storage/testdata/blank-large-file.dmg b/storage/testdata/blank-large-file.dmg
new file mode 100644
index 0000000..90be405
Binary files /dev/null and b/storage/testdata/blank-large-file.dmg differ
diff --git a/storage/testdata/small-file.png b/storage/testdata/small-file.png
new file mode 100644
index 0000000..0776b1b
Binary files /dev/null and b/storage/testdata/small-file.png differ
diff --git a/testhelpers/client.go b/testhelpers/client.go
new file mode 100644
index 0000000..71ef33d
--- /dev/null
+++ b/testhelpers/client.go
@@ -0,0 +1,194 @@
+package testhelpers
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "time"
+
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/resources"
+ "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
+ "github.com/Azure/go-autorest/autorest"
+ "github.com/Azure/go-autorest/autorest/adal"
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/hashicorp/go-azure-helpers/authentication"
+)
+
+type Client struct {
+ ResourceGroupsClient resources.GroupsClient
+ StorageClient storage.AccountsClient
+
+ auth *autorest.BearerAuthorizer
+ Environment azure.Environment
+}
+
+func toPointeredString(input string) *string {
+ return &input
+}
+
+type TestResources struct {
+ ResourceGroup string
+ StorageAccountName string
+ StorageAccountKey string
+}
+
+func (client Client) BuildTestResources(ctx context.Context, resourceGroup, name string, kind storage.Kind) (*TestResources, error) {
+ location := toPointeredString(os.Getenv("ARM_TEST_LOCATION"))
+ _, err := client.ResourceGroupsClient.CreateOrUpdate(ctx, resourceGroup, resources.Group{
+ Location: location,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("Error creating Resource Group %q: %s", resourceGroup, err)
+ }
+
+ props := storage.AccountPropertiesCreateParameters{}
+ if kind == storage.BlobStorage {
+ props.AccessTier = storage.Hot
+ }
+ future, err := client.StorageClient.Create(ctx, resourceGroup, name, storage.AccountCreateParameters{
+ Location: location,
+ Sku: &storage.Sku{
+ Name: storage.StandardLRS,
+ },
+ Kind: kind,
+ AccountPropertiesCreateParameters: &props,
+ })
+
+ if err != nil {
+ return nil, fmt.Errorf("Error creating Account %q (Resource Group %q): %s", name, resourceGroup, err)
+ }
+
+ err = future.WaitForCompletionRef(ctx, client.StorageClient.Client)
+ if err != nil {
+ return nil, fmt.Errorf("Error waiting for the creation of Account %q (Resource Group %q): %s", name, resourceGroup, err)
+ }
+
+ keys, err := client.StorageClient.ListKeys(ctx, resourceGroup, name)
+ if err != nil {
+ return nil, fmt.Errorf("Error listing keys for Storage Account %q (Resource Group %q): %s", name, resourceGroup, err)
+ }
+
+ // sure we could poll to get around the inconsistency, but where's the fun in that
+ time.Sleep(5 * time.Second)
+
+ accountKeys := *keys.Keys
+ return &TestResources{
+ ResourceGroup: resourceGroup,
+ StorageAccountName: name,
+ StorageAccountKey: *(accountKeys[0]).Value,
+ }, nil
+}
+
+func (client Client) DestroyTestResources(ctx context.Context, resourceGroup, name string) error {
+ _, err := client.StorageClient.Delete(ctx, resourceGroup, name)
+ if err != nil {
+ return fmt.Errorf("Error deleting Account %q (Resource Group %q): %s", name, resourceGroup, err)
+ }
+
+ future, err := client.ResourceGroupsClient.Delete(ctx, resourceGroup)
+ if err != nil {
+ return fmt.Errorf("Error deleting Resource Group %q: %s", resourceGroup, err)
+ }
+
+ err = future.WaitForCompletionRef(ctx, client.ResourceGroupsClient.Client)
+ if err != nil {
+ return fmt.Errorf("Error waiting for deletion of Resource Group %q: %s", resourceGroup, err)
+ }
+
+ return nil
+}
+
+func Build() (*Client, error) {
+ authClient, env, err := buildAuthClient()
+ if err != nil {
+ return nil, fmt.Errorf("Error building Auth Client: %s", err)
+ }
+
+ if env == nil {
+ return nil, fmt.Errorf("Environment was nil: %s", err)
+ }
+
+ apiClient, err := buildAPIClient(authClient, *env)
+ if err != nil {
+ return nil, fmt.Errorf("Error building API Client: %s", err)
+ }
+
+ return apiClient, nil
+}
+
+func buildAPIClient(config *authentication.Config, env azure.Environment) (*Client, error) {
+ oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, config.TenantID)
+ if err != nil {
+ return nil, err
+ }
+
+ // OAuthConfigForTenant returns a pointer, which can be nil.
+ if oauthConfig == nil {
+ return nil, fmt.Errorf("Unable to configure OAuthConfig for tenant %s", config.TenantID)
+ }
+
+ sender := buildSender()
+
+ armAuth, err := config.GetAuthorizationToken(sender, oauthConfig, env.ResourceManagerEndpoint)
+ if err != nil {
+ return nil, err
+ }
+
+ storageAuth, err := config.GetAuthorizationToken(sender, oauthConfig, "https://storage.azure.com/")
+ if err != nil {
+ return nil, err
+ }
+
+ client := Client{
+ Environment: env,
+ auth: storageAuth,
+ }
+
+ resourceGroupsClient := resources.NewGroupsClientWithBaseURI(env.ResourceManagerEndpoint, config.SubscriptionID)
+ resourceGroupsClient.Client = client.PrepareWithStorageResourceManagerAuth(resourceGroupsClient.Client)
+ resourceGroupsClient.Authorizer = armAuth
+ client.ResourceGroupsClient = resourceGroupsClient
+
+ storageClient := storage.NewAccountsClientWithBaseURI(env.ResourceManagerEndpoint, config.SubscriptionID)
+ storageClient.Client = client.PrepareWithStorageResourceManagerAuth(storageClient.Client)
+ storageClient.Authorizer = armAuth
+ client.StorageClient = storageClient
+
+ return &client, nil
+}
+
+func (client Client) PrepareWithStorageResourceManagerAuth(input autorest.Client) autorest.Client {
+ return client.PrepareWithAuthorizer(input, client.auth)
+}
+
+func (client Client) PrepareWithAuthorizer(input autorest.Client, authorizer autorest.Authorizer) autorest.Client {
+ input.Authorizer = authorizer
+ input.Sender = buildSender()
+ input.SkipResourceProviderRegistration = true
+ return input
+}
+
+func buildAuthClient() (*authentication.Config, *azure.Environment, error) {
+ builder := &authentication.Builder{
+ SubscriptionID: os.Getenv("ARM_SUBSCRIPTION_ID"),
+ ClientID: os.Getenv("ARM_CLIENT_ID"),
+ ClientSecret: os.Getenv("ARM_CLIENT_SECRET"),
+ TenantID: os.Getenv("ARM_TENANT_ID"),
+ Environment: os.Getenv("ARM_ENVIRONMENT"),
+
+ // Feature Toggles
+ SupportsClientSecretAuth: true,
+ }
+
+ c, err := builder.Build()
+ if err != nil {
+ return nil, nil, fmt.Errorf("Error building AzureRM Client: %s", err)
+ }
+
+ env, err := authentication.DetermineEnvironment(c.Environment)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ return c, env, nil
+}
diff --git a/testhelpers/random.go b/testhelpers/random.go
new file mode 100644
index 0000000..23f1dde
--- /dev/null
+++ b/testhelpers/random.go
@@ -0,0 +1,27 @@
+package testhelpers
+
+import (
+ "math/rand"
+ "time"
+)
+
+func RandomInt() int {
+ reseed()
+ return rand.New(rand.NewSource(time.Now().UnixNano())).Int()
+}
+
+func RandomString() string {
+ size := 5
+ charSet := "abcdefghijklmnopqrstuvwxyz0123456789"
+
+ reseed()
+ result := make([]byte, size)
+ for i := 0; i < size; i++ {
+ result[i] = charSet[rand.Intn(len(charSet))]
+ }
+ return string(result)
+}
+
+func reseed() {
+ rand.Seed(time.Now().UTC().UnixNano())
+}
diff --git a/testhelpers/sender.go b/testhelpers/sender.go
new file mode 100644
index 0000000..b566259
--- /dev/null
+++ b/testhelpers/sender.go
@@ -0,0 +1,65 @@
+package testhelpers
+
+import (
+ "log"
+ "net/http"
+ "net/http/httputil"
+ "os"
+
+ "github.com/Azure/go-autorest/autorest"
+)
+
+func buildSender() autorest.Sender {
+ return autorest.DecorateSender(&http.Client{
+ Transport: &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ },
+ }, withRequestLogging())
+}
+
+func withRequestLogging() autorest.SendDecorator {
+ return func(s autorest.Sender) autorest.Sender {
+ return autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
+ shouldLog := os.Getenv("TEST_LOG") != ""
+
+ if shouldLog {
+ // strip the authorization header prior to printing
+ authHeaderName := "Authorization"
+ auth := r.Header.Get(authHeaderName)
+ if auth != "" {
+ r.Header.Del(authHeaderName)
+ }
+
+ // dump request to wire format
+ if dump, err := httputil.DumpRequestOut(r, true); err == nil {
+ log.Printf("[DEBUG] AzureRM Request: \n%s\n", dump)
+ } else {
+ // fallback to basic message
+ log.Printf("[DEBUG] AzureRM Request: %s to %s\n", r.Method, r.URL)
+ }
+
+ // add the auth header back
+ if auth != "" {
+ r.Header.Add(authHeaderName, auth)
+ }
+ }
+
+ resp, err := s.Do(r)
+
+ if shouldLog {
+ if resp != nil {
+ // dump response to wire format
+ if dump, err2 := httputil.DumpResponse(resp, true); err2 == nil {
+ log.Printf("[DEBUG] AzureRM Response for %s: \n%s\n", r.URL, dump)
+ } else {
+ // fallback to basic message
+ log.Printf("[DEBUG] AzureRM Response: %s for %s\n", resp.Status, r.URL)
+ }
+ } else {
+ log.Printf("[DEBUG] Request to %s completed with no response", r.URL)
+ }
+ }
+ return resp, err
+ })
+ }
+}
diff --git a/version/version.go b/version/version.go
new file mode 100644
index 0000000..9307eba
--- /dev/null
+++ b/version/version.go
@@ -0,0 +1,3 @@
+package version
+
+const Number = "v0.0.1"