From 507efcb180ad831419093396c8195fa995cc916d Mon Sep 17 00:00:00 2001 From: Peter McAtominey Date: Fri, 18 Nov 2016 15:26:25 +0000 Subject: [PATCH] state/azure: support passing of lease ID when writing storage blob (#10115) Also fixed tests failing auth caused by getStorageAccountAccessKey returning the key name rather than the value TF_ACC= go test ./state/remote -v -run=TestAz -timeout=10m -parallel=4 === RUN TestAzureClient_impl --- PASS: TestAzureClient_impl (0.00s) === RUN TestAzureClient 2016/11/18 13:57:34 [DEBUG] New state was assigned lineage "96037426-f95e-45c3-9183-6c39b49f590b" 2016/11/18 13:57:34 [TRACE] Preserving existing state lineage "96037426-f95e-45c3-9183-6c39b49f590b" --- PASS: TestAzureClient (130.60s) === RUN TestAzureClientEmptyLease 2016/11/18 13:59:44 [DEBUG] New state was assigned lineage "d9997445-1ebf-4b2c-b4df-15ae152f6417" 2016/11/18 13:59:44 [TRACE] Preserving existing state lineage "d9997445-1ebf-4b2c-b4df-15ae152f6417" --- PASS: TestAzureClientEmptyLease (128.15s) === RUN TestAzureClientLease 2016/11/18 14:01:55 [DEBUG] New state was assigned lineage "85912a12-2e0e-464c-9886-8add39ea3a87" 2016/11/18 14:01:55 [TRACE] Preserving existing state lineage "85912a12-2e0e-464c-9886-8add39ea3a87" --- PASS: TestAzureClientLease (138.09s) PASS ok github.com/hashicorp/terraform/state/remote 397.111s --- state/remote/azure.go | 24 ++++-- state/remote/azure_test.go | 86 +++++++++++++++---- .../source/docs/state/remote/azure.html.md | 1 + 3 files changed, 91 insertions(+), 20 deletions(-) diff --git a/state/remote/azure.go b/state/remote/azure.go index 3f78e35dc..dee7a559c 100644 --- a/state/remote/azure.go +++ b/state/remote/azure.go @@ -46,11 +46,13 @@ func azureFactory(conf map[string]string) (Client, error) { } blobClient := storageClient.GetBlobService() + leaseID, _ := confOrEnv(conf, "lease_id", "ARM_LEASE_ID") return &AzureClient{ blobClient: &blobClient, containerName: containerName, keyName: keyName, + leaseID: leaseID, }, nil } @@ -86,7 +88,7 @@ func getStorageAccountAccessKey(conf map[string]string, resourceGroupName, stora } accessKeys := *keys.Keys - return *accessKeys[0].KeyName, nil + return *accessKeys[0].Value, nil } func getCredentialsFromConf(conf map[string]string) (*riviera.AzureResourceManagerCredentials, error) { @@ -130,6 +132,7 @@ type AzureClient struct { blobClient *mainStorage.BlobStorageClient containerName string keyName string + leaseID string } func (c *AzureClient) Get() (*Payload, error) { @@ -163,17 +166,28 @@ func (c *AzureClient) Get() (*Payload, error) { } func (c *AzureClient) Put(data []byte) error { + headers := map[string]string{ + "Content-Type": "application/json", + } + + if c.leaseID != "" { + headers["x-ms-lease-id"] = c.leaseID + } + return c.blobClient.CreateBlockBlobFromReader( c.containerName, c.keyName, uint64(len(data)), bytes.NewReader(data), - map[string]string{ - "Content-Type": "application/json", - }, + headers, ) } func (c *AzureClient) Delete() error { - return c.blobClient.DeleteBlob(c.containerName, c.keyName, nil) + headers := map[string]string{} + if c.leaseID != "" { + headers["x-ms-lease-id"] = c.leaseID + } + + return c.blobClient.DeleteBlob(c.containerName, c.keyName, headers) } diff --git a/state/remote/azure_test.go b/state/remote/azure_test.go index 7680bfbb7..3e37f5876 100644 --- a/state/remote/azure_test.go +++ b/state/remote/azure_test.go @@ -5,22 +5,84 @@ import ( "os" "strings" "testing" - "time" mainStorage "github.com/Azure/azure-sdk-for-go/storage" + "github.com/hashicorp/terraform/helper/acctest" riviera "github.com/jen20/riviera/azure" "github.com/jen20/riviera/storage" + "github.com/satori/uuid" ) func TestAzureClient_impl(t *testing.T) { var _ Client = new(AzureClient) } +// This test creates a bucket in Azure and populates it. +// It may incur costs, so it will only run if Azure credential environment +// variables are present. func TestAzureClient(t *testing.T) { - // This test creates a bucket in Azure and populates it. - // It may incur costs, so it will only run if Azure credential environment - // variables are present. + config := getAzureConfig(t) + setup(t, config) + defer teardown(t, config) + + client, err := azureFactory(config) + if err != nil { + t.Fatalf("Error for valid config: %v", err) + } + + testClient(t, client) +} + +// This test is the same as TestAzureClient with the addition of passing an +// empty string in the lease_id, we expect the client to pass tests +func TestAzureClientEmptyLease(t *testing.T) { + config := getAzureConfig(t) + config["lease_id"] = "" + + setup(t, config) + defer teardown(t, config) + + client, err := azureFactory(config) + if err != nil { + t.Fatalf("Error for valid config: %v", err) + } + + testClient(t, client) +} + +// This test is the same as TestAzureClient with the addition of using the +// lease_id config option +func TestAzureClientLease(t *testing.T) { + leaseID := uuid.NewV4().String() + config := getAzureConfig(t) + config["lease_id"] = leaseID + + setup(t, config) + defer teardown(t, config) + + client, err := azureFactory(config) + if err != nil { + t.Fatalf("Error for valid config: %v", err) + } + azureClient := client.(*AzureClient) + + // put empty blob so we can acquire lease against it + err = azureClient.blobClient.CreateBlockBlob(azureClient.containerName, azureClient.keyName) + if err != nil { + t.Fatalf("Error creating blob for leasing: %v", err) + } + + _, err = azureClient.blobClient.AcquireLease(azureClient.containerName, azureClient.keyName, -1, leaseID) + if err != nil { + t.Fatalf("Error acquiring lease: %v", err) + } + + // no need to release lease as blob is deleted in testing + testClient(t, client) +} + +func getAzureConfig(t *testing.T) map[string]string { config := map[string]string{ "arm_subscription_id": os.Getenv("ARM_SUBSCRIPTION_ID"), "arm_client_id": os.Getenv("ARM_CLIENT_ID"), @@ -34,20 +96,14 @@ func TestAzureClient(t *testing.T) { } } - config["resource_group_name"] = fmt.Sprintf("terraform-%x", time.Now().Unix()) - config["storage_account_name"] = fmt.Sprintf("terraform%x", time.Now().Unix()) + rs := acctest.RandString(8) + + config["resource_group_name"] = fmt.Sprintf("terraform-%s", rs) + config["storage_account_name"] = fmt.Sprintf("terraform%s", rs) config["container_name"] = "terraform" config["key"] = "test.tfstate" - setup(t, config) - defer teardown(t, config) - - client, err := azureFactory(config) - if err != nil { - t.Fatalf("Error for valid config: %v", err) - } - - testClient(t, client) + return config } func setup(t *testing.T, conf map[string]string) { diff --git a/website/source/docs/state/remote/azure.html.md b/website/source/docs/state/remote/azure.html.md index 292a30102..0decbe1ad 100644 --- a/website/source/docs/state/remote/azure.html.md +++ b/website/source/docs/state/remote/azure.html.md @@ -48,3 +48,4 @@ The following configuration options are supported: * `container_name` - (Required) The name of the container to use within the storage account * `key` - (Required) The key where to place/look for state file inside the container * `access_key` / `ARM_ACCESS_KEY` - (Required) Storage account access key + * `lease_id` / `ARM_LEASE_ID` - (Optional) If set, will be used when writing to storage blob.