From 68780962a00cf600c80a22d49b222990cf9143bf Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Wed, 4 Sep 2019 13:01:13 +0200 Subject: [PATCH 1/7] r/storage_blob: support for Append Blobs --- azurerm/internal/services/storage/blobs.go | 16 +++++++++++++++- azurerm/resource_arm_storage_blob.go | 12 ++++++++---- azurerm/resource_arm_storage_blob_test.go | 16 +--------------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/azurerm/internal/services/storage/blobs.go b/azurerm/internal/services/storage/blobs.go index c292907aa6f8..305ec9b47652 100644 --- a/azurerm/internal/services/storage/blobs.go +++ b/azurerm/internal/services/storage/blobs.go @@ -40,7 +40,9 @@ func (sbu BlobUpload) Create(ctx context.Context) error { blobType := strings.ToLower(sbu.BlobType) - // TODO: new feature for 'append' blobs? + if blobType == "append" { + return sbu.createEmptyAppendBlob(ctx) + } if blobType == "block" { if sbu.Source != "" { @@ -73,6 +75,18 @@ func (sbu BlobUpload) copy(ctx context.Context) error { return nil } +func (sbu BlobUpload) createEmptyAppendBlob(ctx context.Context) error { + input := blobs.PutAppendBlobInput{ + ContentType: utils.String(sbu.ContentType), + MetaData: sbu.MetaData, + } + if _, err := sbu.Client.PutAppendBlob(ctx, sbu.AccountName, sbu.ContainerName, sbu.BlobName, input); err != nil { + return fmt.Errorf("Error PutAppendBlob: %s", err) + } + + return nil +} + func (sbu BlobUpload) createEmptyBlockBlob(ctx context.Context) error { input := blobs.PutBlockBlobInput{ ContentType: utils.String(sbu.ContentType), diff --git a/azurerm/resource_arm_storage_blob.go b/azurerm/resource_arm_storage_blob.go index f122d83b236e..00ae455e4a13 100644 --- a/azurerm/resource_arm_storage_blob.go +++ b/azurerm/resource_arm_storage_blob.go @@ -51,10 +51,14 @@ func resourceArmStorageBlob() *schema.Resource { }, "type": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{"block", "page"}, true), + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "append", + "block", + "page", + }, true), }, "size": { diff --git a/azurerm/resource_arm_storage_blob_test.go b/azurerm/resource_arm_storage_blob_test.go index d76d74731098..320196ebe5dc 100644 --- a/azurerm/resource_arm_storage_blob_test.go +++ b/azurerm/resource_arm_storage_blob_test.go @@ -17,9 +17,7 @@ import ( "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/blobs" ) -// TODO: with the new SDK: changing the Tier of Blobs. Content type for Block blobs - -var supportsNewStorageFeatures = false +// TODO: with the new SDK: changing the Tier of Blobs func TestAccAzureRMStorageBlob_disappears(t *testing.T) { resourceName := "azurerm_storage_blob.test" @@ -45,10 +43,6 @@ func TestAccAzureRMStorageBlob_disappears(t *testing.T) { } func TestAccAzureRMStorageBlob_appendEmpty(t *testing.T) { - if !supportsNewStorageFeatures { - t.Skip("Resource doesn't support Append Blobs Yet..") - } - resourceName := "azurerm_storage_blob.test" ri := tf.AccRandTimeInt() rs := strings.ToLower(acctest.RandString(11)) @@ -76,10 +70,6 @@ func TestAccAzureRMStorageBlob_appendEmpty(t *testing.T) { } func TestAccAzureRMStorageBlob_appendEmptyMetaData(t *testing.T) { - if !supportsNewStorageFeatures { - t.Skip("Resource doesn't support Append Blobs Yet..") - } - resourceName := "azurerm_storage_blob.test" ri := tf.AccRandTimeInt() rs := strings.ToLower(acctest.RandString(11)) @@ -520,10 +510,6 @@ func TestAccAzureRMStorageBlob_requiresImport(t *testing.T) { } func TestAccAzureRMStorageBlob_update(t *testing.T) { - if !supportsNewStorageFeatures { - t.Skip("Current implementation doesn't support updating the Content Type..") - } - resourceName := "azurerm_storage_blob.test" ri := tf.AccRandTimeInt() rs := strings.ToLower(acctest.RandString(11)) From efa5eeaa8f351141dae254e164d51fae2a9fc1cf Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Wed, 4 Sep 2019 13:33:53 +0200 Subject: [PATCH 2/7] r/storage_blob: support for Access Tier --- azurerm/resource_arm_storage_blob.go | 67 +++++++++++------- azurerm/resource_arm_storage_blob_test.go | 85 ++++++++++++++++++++++- website/docs/r/storage_blob.html.markdown | 22 +++--- 3 files changed, 137 insertions(+), 37 deletions(-) diff --git a/azurerm/resource_arm_storage_blob.go b/azurerm/resource_arm_storage_blob.go index 00ae455e4a13..a30bca13a762 100644 --- a/azurerm/resource_arm_storage_blob.go +++ b/azurerm/resource_arm_storage_blob.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" @@ -37,27 +38,28 @@ func resourceArmStorageBlob() *schema.Resource { }, "storage_account_name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - // TODO: add validation + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateArmStorageAccountName, }, "storage_container_name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - // TODO: add validation + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateArmStorageContainerName, }, "type": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + DiffSuppressFunc: suppress.CaseDifference, // TODO: remove in 2.0 ValidateFunc: validation.StringInSlice([]string{ - "append", - "block", - "page", + "Append", + "Block", + "Page", }, true), }, @@ -69,6 +71,17 @@ func resourceArmStorageBlob() *schema.Resource { ValidateFunc: validate.IntDivisibleBy(512), }, + "access_tier": { + Type: schema.TypeString, + Optional: true, + Default: string(blobs.Hot), + ValidateFunc: validation.StringInSlice([]string{ + string(blobs.Archive), + string(blobs.Cool), + string(blobs.Hot), + }, false), + }, + "content_type": { Type: schema.TypeString, Optional: true, @@ -203,7 +216,16 @@ func resourceArmStorageBlobUpdate(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("Error building Blobs Client: %s", err) } - // TODO: changing the access tier + if d.HasChange("access_tier") { + log.Printf("[DEBUG] Updating Access Tier for Blob %q (Container %q / Account %q)...", id.BlobName, id.ContainerName, id.AccountName) + accessTier := blobs.AccessTier(d.Get("access_tier").(string)) + + if _, err := blobsClient.SetTier(ctx, id.AccountName, id.ContainerName, id.BlobName, accessTier); err != nil { + return fmt.Errorf("Error updating Access Tier for Blob %q (Container %q / Account %q): %s", id.BlobName, id.ContainerName, id.AccountName, err) + } + + log.Printf("[DEBUG] Updated Access Tier for Blob %q (Container %q / Account %q).", id.BlobName, id.ContainerName, id.AccountName) + } if d.HasChange("content_type") { log.Printf("[DEBUG] Updating Properties for Blob %q (Container %q / Account %q)...", id.BlobName, id.ContainerName, id.AccountName) @@ -273,23 +295,20 @@ func resourceArmStorageBlobRead(d *schema.ResourceData, meta interface{}) error d.Set("storage_account_name", id.AccountName) d.Set("resource_group_name", resourceGroup) + d.Set("access_tier", string(props.AccessTier)) d.Set("content_type", props.ContentType) + d.Set("type", strings.TrimSuffix(string(props.BlobType), "Blob")) + d.Set("url", d.Id()) + if err := d.Set("metadata", storage.FlattenMetaData(props.MetaData)); err != nil { + return fmt.Errorf("Error setting `metadata`: %+v", err) + } // The CopySource is only returned if the blob hasn't been modified (e.g. metadata configured etc) // as such, we need to conditionally set this to ensure it's trackable if possible if props.CopySource != "" { d.Set("source_uri", props.CopySource) } - blobType := strings.ToLower(strings.Replace(string(props.BlobType), "Blob", "", 1)) - d.Set("type", blobType) - - d.Set("url", d.Id()) - - if err := d.Set("metadata", storage.FlattenMetaData(props.MetaData)); err != nil { - return fmt.Errorf("Error setting `metadata`: %+v", err) - } - return nil } diff --git a/azurerm/resource_arm_storage_blob_test.go b/azurerm/resource_arm_storage_blob_test.go index 320196ebe5dc..2035dd105f0f 100644 --- a/azurerm/resource_arm_storage_blob_test.go +++ b/azurerm/resource_arm_storage_blob_test.go @@ -17,8 +17,6 @@ import ( "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/blobs" ) -// TODO: with the new SDK: changing the Tier of Blobs - func TestAccAzureRMStorageBlob_disappears(t *testing.T) { resourceName := "azurerm_storage_blob.test" ri := tf.AccRandTimeInt() @@ -150,6 +148,48 @@ func TestAccAzureRMStorageBlob_blockEmptyMetaData(t *testing.T) { }) } +func TestAccAzureRMStorageBlob_blockEmptyAccessTier(t *testing.T) { + resourceName := "azurerm_storage_blob.test" + ri := tf.AccRandTimeInt() + rs := strings.ToLower(acctest.RandString(11)) + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMStorageBlobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMStorageBlob_blockEmptyAccessTier(ri, rs, location, blobs.Cool), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageBlobExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "access_tier", "Cool"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"attempts", "parallelism", "size", "type"}, + }, + { + Config: testAccAzureRMStorageBlob_blockEmptyAccessTier(ri, rs, location, blobs.Hot), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageBlobExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "access_tier", "Hot"), + ), + }, + { + Config: testAccAzureRMStorageBlob_blockEmptyAccessTier(ri, rs, location, blobs.Cool), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageBlobExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "access_tier", "Cool"), + ), + }, + }, + }) +} + func TestAccAzureRMStorageBlob_blockFromPublicBlob(t *testing.T) { resourceName := "azurerm_storage_blob.test" ri := tf.AccRandTimeInt() @@ -802,6 +842,22 @@ resource "azurerm_storage_blob" "test" { `, template) } +func testAccAzureRMStorageBlob_blockEmptyAccessTier(rInt int, rString string, location string, accessTier blobs.AccessTier) string { + template := testAccAzureRMStorageBlob_templateBlockBlobStorage(rInt, rString, location, "private") + return fmt.Sprintf(` +%s + +resource "azurerm_storage_blob" "test" { + name = "example.vhd" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" + storage_container_name = "${azurerm_storage_container.test.name}" + type = "block" + access_tier = "%s" +} +`, template, string(accessTier)) +} + func testAccAzureRMStorageBlob_blockFromPublicBlob(rInt int, rString, location string) string { template := testAccAzureRMStorageBlob_template(rInt, rString, location, "blob") return fmt.Sprintf(` @@ -1090,6 +1146,31 @@ resource "azurerm_storage_blob" "import" { `, template) } +func testAccAzureRMStorageBlob_templateBlockBlobStorage(rInt int, rString, location, accessLevel string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestacc%s" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + account_kind = "StorageV2" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_container" "test" { + name = "test" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" + container_access_type = "%s" +} +`, rInt, location, rString, accessLevel) +} + func testAccAzureRMStorageBlob_update(rInt int, rString, location string) string { template := testAccAzureRMStorageBlob_template(rInt, rString, location, "private") return fmt.Sprintf(` diff --git a/website/docs/r/storage_blob.html.markdown b/website/docs/r/storage_blob.html.markdown index 187d77d7fadd..7d4ef80b7f90 100644 --- a/website/docs/r/storage_blob.html.markdown +++ b/website/docs/r/storage_blob.html.markdown @@ -14,12 +14,12 @@ Manages a Blob within a Storage Container. ```hcl resource "azurerm_resource_group" "test" { - name = "acctestRG-d" - location = "westus" + name = "example-resources" + location = "West Europe" } resource "azurerm_storage_account" "test" { - name = "acctestaccs" + name = "examplestoracc" resource_group_name = "${azurerm_resource_group.test.name}" location = "${azurerm_resource_group.test.location}" account_tier = "Standard" @@ -27,21 +27,19 @@ resource "azurerm_storage_account" "test" { } resource "azurerm_storage_container" "test" { - name = "vhds" + name = "content" resource_group_name = "${azurerm_resource_group.test.name}" storage_account_name = "${azurerm_storage_account.test.name}" container_access_type = "private" } -resource "azurerm_storage_blob" "testsb" { - name = "sample.vhd" - +resource "azurerm_storage_blob" "test" { + name = "my-awesome-content.zip" resource_group_name = "${azurerm_resource_group.test.name}" storage_account_name = "${azurerm_storage_account.test.name}" storage_container_name = "${azurerm_storage_container.test.name}" - - type = "page" - size = 5120 + type = "blob" + source = "some-local-file.zip" } ``` @@ -56,11 +54,13 @@ The following arguments are supported: * `storage_container_name` - (Required) The name of the storage container in which this blob should be created. -* `type` - (Optional) The type of the storage blob to be created. One of either `block` or `page`. When not copying from an existing blob, +* `type` - (Optional) The type of the storage blob to be created. One of either `Append`, `Block` or `Page`. When not copying from an existing blob, this becomes required. * `size` - (Optional) Used only for `page` blobs to specify the size in bytes of the blob to be created. Must be a multiple of 512. Defaults to 0. +* `access_tier` - (Optional) The access tier of the storage blob. Possible values are `Archive`, `Cool` and `Hot`. Defaults to `Hot`. + * `content_type` - (Optional) The content type of the storage blob. Cannot be defined if `source_uri` is defined. Defaults to `application/octet-stream`. * `source` - (Optional) An absolute path to a file on the local system. Cannot be defined if `source_uri` is defined. From 2398ad1a05d484ab545affb1250f594dc1aa7da6 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Wed, 4 Sep 2019 13:35:02 +0200 Subject: [PATCH 3/7] r/storage_blob: making `type` required fixes #2006 --- azurerm/resource_arm_storage_blob.go | 2 +- website/docs/r/storage_blob.html.markdown | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/azurerm/resource_arm_storage_blob.go b/azurerm/resource_arm_storage_blob.go index a30bca13a762..d46cb36ebf5c 100644 --- a/azurerm/resource_arm_storage_blob.go +++ b/azurerm/resource_arm_storage_blob.go @@ -53,7 +53,7 @@ func resourceArmStorageBlob() *schema.Resource { "type": { Type: schema.TypeString, - Optional: true, + Required: true, ForceNew: true, DiffSuppressFunc: suppress.CaseDifference, // TODO: remove in 2.0 ValidateFunc: validation.StringInSlice([]string{ diff --git a/website/docs/r/storage_blob.html.markdown b/website/docs/r/storage_blob.html.markdown index 7d4ef80b7f90..fc4c61abec53 100644 --- a/website/docs/r/storage_blob.html.markdown +++ b/website/docs/r/storage_blob.html.markdown @@ -54,8 +54,7 @@ The following arguments are supported: * `storage_container_name` - (Required) The name of the storage container in which this blob should be created. -* `type` - (Optional) The type of the storage blob to be created. One of either `Append`, `Block` or `Page`. When not copying from an existing blob, - this becomes required. +* `type` - (Required) The type of the storage blob to be created. Possible values are `Append`, `Block` or `Page`. Changing this forces a new resource to be created. * `size` - (Optional) Used only for `page` blobs to specify the size in bytes of the blob to be created. Must be a multiple of 512. Defaults to 0. From d945dce5805c8803c13d3c51371bb8e4aa54dccb Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Wed, 4 Sep 2019 14:04:49 +0200 Subject: [PATCH 4/7] r/storage_blob: support for inline content fixes #3876 --- azurerm/internal/services/storage/blobs.go | 56 ++++++++++++-- azurerm/resource_arm_storage_blob.go | 29 +++++--- azurerm/resource_arm_storage_blob_test.go | 86 ++++++++++++++++++++++ website/docs/r/storage_blob.html.markdown | 8 +- 4 files changed, 159 insertions(+), 20 deletions(-) diff --git a/azurerm/internal/services/storage/blobs.go b/azurerm/internal/services/storage/blobs.go index 305ec9b47652..559d3cb39963 100644 --- a/azurerm/internal/services/storage/blobs.go +++ b/azurerm/internal/services/storage/blobs.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "io" + "io/ioutil" "os" "runtime" "strings" @@ -24,16 +25,18 @@ type BlobUpload struct { BlobName string ContainerName string - BlobType string - ContentType string - MetaData map[string]string - Parallelism int - Size int - Source string - SourceUri string + BlobType string + ContentType string + MetaData map[string]string + Parallelism int + Size int + Source string + SourceContent string + SourceUri string } func (sbu BlobUpload) Create(ctx context.Context) error { + // TODO: should we move this into the Block and Page blocks? if sbu.SourceUri != "" { return sbu.copy(ctx) } @@ -41,10 +44,14 @@ func (sbu BlobUpload) Create(ctx context.Context) error { blobType := strings.ToLower(sbu.BlobType) if blobType == "append" { + // TODO: if Source/SourceContent are set return an error return sbu.createEmptyAppendBlob(ctx) } if blobType == "block" { + if sbu.SourceContent != "" { + return sbu.uploadBlockBlobFromContent(ctx) + } if sbu.Source != "" { return sbu.uploadBlockBlob(ctx) } @@ -53,6 +60,9 @@ func (sbu BlobUpload) Create(ctx context.Context) error { } if blobType == "page" { + if sbu.SourceContent != "" { + return sbu.uploadPageBlobFromContent(ctx) + } if sbu.Source != "" { return sbu.uploadPageBlob(ctx) } @@ -99,6 +109,22 @@ func (sbu BlobUpload) createEmptyBlockBlob(ctx context.Context) error { return nil } +func (sbu BlobUpload) uploadBlockBlobFromContent(ctx context.Context) error { + tmpFile, err := ioutil.TempFile(os.TempDir(), "upload-") + if err != nil { + return fmt.Errorf("Error creating temporary file: %s", err) + } + defer os.Remove(tmpFile.Name()) + + if _, err = tmpFile.Write([]byte(sbu.SourceContent)); err != nil { + return fmt.Errorf("Error writing Source Content to Temp File: %s", err) + } + defer tmpFile.Close() + + sbu.Source = tmpFile.Name() + return sbu.uploadBlockBlob(ctx) +} + func (sbu BlobUpload) uploadBlockBlob(ctx context.Context) error { file, err := os.Open(sbu.Source) if err != nil { @@ -134,6 +160,22 @@ func (sbu BlobUpload) createEmptyPageBlob(ctx context.Context) error { return nil } +func (sbu BlobUpload) uploadPageBlobFromContent(ctx context.Context) error { + tmpFile, err := ioutil.TempFile(os.TempDir(), "upload-") + if err != nil { + return fmt.Errorf("Error creating temporary file: %s", err) + } + defer os.Remove(tmpFile.Name()) + + if _, err = tmpFile.Write([]byte(sbu.SourceContent)); err != nil { + return fmt.Errorf("Error writing Source Content to Temp File: %s", err) + } + defer tmpFile.Close() + + sbu.Source = tmpFile.Name() + return sbu.uploadPageBlob(ctx) +} + func (sbu BlobUpload) uploadPageBlob(ctx context.Context) error { if sbu.Size != 0 { return fmt.Errorf("`size` cannot be set for an uploaded page blob") diff --git a/azurerm/resource_arm_storage_blob.go b/azurerm/resource_arm_storage_blob.go index d46cb36ebf5c..d3acb7402e56 100644 --- a/azurerm/resource_arm_storage_blob.go +++ b/azurerm/resource_arm_storage_blob.go @@ -74,7 +74,7 @@ func resourceArmStorageBlob() *schema.Resource { "access_tier": { Type: schema.TypeString, Optional: true, - Default: string(blobs.Hot), + Computed: true, ValidateFunc: validation.StringInSlice([]string{ string(blobs.Archive), string(blobs.Cool), @@ -92,14 +92,21 @@ func resourceArmStorageBlob() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, - ConflictsWith: []string{"source_uri"}, + ConflictsWith: []string{"source_uri", "source_content"}, + }, + + "source_content": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"source", "source_uri"}, }, "source_uri": { Type: schema.TypeString, Optional: true, ForceNew: true, - ConflictsWith: []string{"source"}, + ConflictsWith: []string{"source", "source_content"}, }, "url": { @@ -176,13 +183,14 @@ func resourceArmStorageBlobCreate(d *schema.ResourceData, meta interface{}) erro BlobName: name, Client: blobsClient, - BlobType: d.Get("type").(string), - ContentType: d.Get("content_type").(string), - MetaData: storage.ExpandMetaData(metaDataRaw), - Parallelism: d.Get("parallelism").(int), - Size: d.Get("size").(int), - Source: d.Get("source").(string), - SourceUri: d.Get("source_uri").(string), + BlobType: d.Get("type").(string), + ContentType: d.Get("content_type").(string), + MetaData: storage.ExpandMetaData(metaDataRaw), + Parallelism: d.Get("parallelism").(int), + Size: d.Get("size").(int), + Source: d.Get("source").(string), + SourceContent: d.Get("source_content").(string), + SourceUri: d.Get("source_uri").(string), } if err := blobInput.Create(ctx); err != nil { return fmt.Errorf("Error creating Blob %q (Container %q / Account %q): %s", name, containerName, accountName, err) @@ -217,6 +225,7 @@ func resourceArmStorageBlobUpdate(d *schema.ResourceData, meta interface{}) erro } if d.HasChange("access_tier") { + // this is only applicable for Gen2/BlobStorage accounts log.Printf("[DEBUG] Updating Access Tier for Blob %q (Container %q / Account %q)...", id.BlobName, id.ContainerName, id.AccountName) accessTier := blobs.AccessTier(d.Get("access_tier").(string)) diff --git a/azurerm/resource_arm_storage_blob_test.go b/azurerm/resource_arm_storage_blob_test.go index 2035dd105f0f..b7effe8e26b5 100644 --- a/azurerm/resource_arm_storage_blob_test.go +++ b/azurerm/resource_arm_storage_blob_test.go @@ -190,6 +190,33 @@ func TestAccAzureRMStorageBlob_blockEmptyAccessTier(t *testing.T) { }) } +func TestAccAzureRMStorageBlob_blockFromInlineContent(t *testing.T) { + resourceName := "azurerm_storage_blob.test" + ri := tf.AccRandTimeInt() + rs := strings.ToLower(acctest.RandString(11)) + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMStorageBlobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMStorageBlob_blockFromInlineContent(ri, rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageBlobExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"attempts", "parallelism", "size", "source_content", "type"}, + }, + }, + }) +} + func TestAccAzureRMStorageBlob_blockFromPublicBlob(t *testing.T) { resourceName := "azurerm_storage_blob.test" ri := tf.AccRandTimeInt() @@ -482,6 +509,33 @@ func TestAccAzureRMStorageBlob_pageFromExistingBlob(t *testing.T) { }) } +func TestAccAzureRMStorageBlob_pageFromInlineContent(t *testing.T) { + resourceName := "azurerm_storage_blob.test" + ri := tf.AccRandTimeInt() + rs := strings.ToLower(acctest.RandString(11)) + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMStorageBlobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMStorageBlob_pageFromInlineContent(ri, rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageBlobExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"attempts", "parallelism", "size", "source_content", "type"}, + }, + }, + }) +} + func TestAccAzureRMStorageBlob_pageFromLocalFile(t *testing.T) { sourceBlob, err := ioutil.TempFile("", "") if err != nil { @@ -858,6 +912,22 @@ resource "azurerm_storage_blob" "test" { `, template, string(accessTier)) } +func testAccAzureRMStorageBlob_blockFromInlineContent(rInt int, rString, location string) string { + template := testAccAzureRMStorageBlob_template(rInt, rString, location, "blob") + return fmt.Sprintf(` +%s + +resource "azurerm_storage_blob" "test" { + name = "rick.morty" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" + storage_container_name = "${azurerm_storage_container.test.name}" + type = "block" + source_content = "Wubba Lubba Dub Dub" +} +`, template) +} + func testAccAzureRMStorageBlob_blockFromPublicBlob(rInt int, rString, location string) string { template := testAccAzureRMStorageBlob_template(rInt, rString, location, "blob") return fmt.Sprintf(` @@ -1082,6 +1152,22 @@ resource "azurerm_storage_blob" "test" { `, template) } +func testAccAzureRMStorageBlob_pageFromInlineContent(rInt int, rString, location string) string { + template := testAccAzureRMStorageBlob_template(rInt, rString, location, "private") + return fmt.Sprintf(` +%s + +resource "azurerm_storage_blob" "test" { + name = "rick.morty" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" + storage_container_name = "${azurerm_storage_container.test.name}" + type = "page" + source_content = "Wubba Lubba Dub Dub" +} +`, template) +} + func testAccAzureRMStorageBlob_pageFromLocalBlob(rInt int, rString, location, fileName string) string { template := testAccAzureRMStorageBlob_template(rInt, rString, location, "private") return fmt.Sprintf(` diff --git a/website/docs/r/storage_blob.html.markdown b/website/docs/r/storage_blob.html.markdown index fc4c61abec53..ccaa76ab7815 100644 --- a/website/docs/r/storage_blob.html.markdown +++ b/website/docs/r/storage_blob.html.markdown @@ -58,14 +58,16 @@ The following arguments are supported: * `size` - (Optional) Used only for `page` blobs to specify the size in bytes of the blob to be created. Must be a multiple of 512. Defaults to 0. -* `access_tier` - (Optional) The access tier of the storage blob. Possible values are `Archive`, `Cool` and `Hot`. Defaults to `Hot`. +* `access_tier` - (Optional) The access tier of the storage blob. Possible values are `Archive`, `Cool` and `Hot`. * `content_type` - (Optional) The content type of the storage blob. Cannot be defined if `source_uri` is defined. Defaults to `application/octet-stream`. -* `source` - (Optional) An absolute path to a file on the local system. Cannot be defined if `source_uri` is defined. +* `source` - (Optional) An absolute path to a file on the local system. Cannot be specified if `source_content` or `source_uri` is specified. + +* `source_content` - (Optional) The content for this blob which should be defined inline. Cannot be specified if `source` or `source_uri` is specified. * `source_uri` - (Optional) The URI of an existing blob, or a file in the Azure File service, to use as the source contents - for the blob to be created. Changing this forces a new resource to be created. Cannot be defined if `source` is defined. + for the blob to be created. Changing this forces a new resource to be created. Cannot be specified if `source` or `source_content` is specified. * `parallelism` - (Optional) The number of workers per CPU core to run for concurrent uploads. Defaults to `8`. From 8e6c3d496567f7486b96db58d6afa26580b36ea1 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Wed, 4 Sep 2019 14:06:56 +0200 Subject: [PATCH 5/7] r/storage_blob: validation for append blobs --- azurerm/internal/services/storage/blobs.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/azurerm/internal/services/storage/blobs.go b/azurerm/internal/services/storage/blobs.go index 559d3cb39963..946ea6e96032 100644 --- a/azurerm/internal/services/storage/blobs.go +++ b/azurerm/internal/services/storage/blobs.go @@ -36,19 +36,21 @@ type BlobUpload struct { } func (sbu BlobUpload) Create(ctx context.Context) error { - // TODO: should we move this into the Block and Page blocks? - if sbu.SourceUri != "" { - return sbu.copy(ctx) - } - blobType := strings.ToLower(sbu.BlobType) if blobType == "append" { - // TODO: if Source/SourceContent are set return an error + if sbu.Source != "" || sbu.SourceContent != "" || sbu.SourceUri != "" { + return fmt.Errorf("A source cannot be specified for an Append blob") + } + return sbu.createEmptyAppendBlob(ctx) } if blobType == "block" { + if sbu.SourceUri != "" { + return sbu.copy(ctx) + } + if sbu.SourceContent != "" { return sbu.uploadBlockBlobFromContent(ctx) } @@ -60,6 +62,10 @@ func (sbu BlobUpload) Create(ctx context.Context) error { } if blobType == "page" { + if sbu.SourceUri != "" { + return sbu.copy(ctx) + } + if sbu.SourceContent != "" { return sbu.uploadPageBlobFromContent(ctx) } @@ -203,7 +209,6 @@ func (sbu BlobUpload) uploadPageBlob(ctx context.Context) error { ContentType: utils.String(sbu.ContentType), MetaData: sbu.MetaData, } - // TODO: access tiers? if _, err := sbu.Client.PutPageBlob(ctx, sbu.AccountName, sbu.ContainerName, sbu.BlobName, input); err != nil { return fmt.Errorf("Error PutPageBlob: %s", err) } From d5f5223925f1334910cbf147440dcd5d7383944c Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Thu, 5 Sep 2019 10:55:48 +0200 Subject: [PATCH 6/7] r/storage_blob: grouping the template functions --- azurerm/resource_arm_storage_blob_test.go | 50 +++++++++++------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/azurerm/resource_arm_storage_blob_test.go b/azurerm/resource_arm_storage_blob_test.go index b7effe8e26b5..700126f871ed 100644 --- a/azurerm/resource_arm_storage_blob_test.go +++ b/azurerm/resource_arm_storage_blob_test.go @@ -1232,31 +1232,6 @@ resource "azurerm_storage_blob" "import" { `, template) } -func testAccAzureRMStorageBlob_templateBlockBlobStorage(rInt int, rString, location, accessLevel string) string { - return fmt.Sprintf(` -resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" -} - -resource "azurerm_storage_account" "test" { - name = "acctestacc%s" - resource_group_name = "${azurerm_resource_group.test.name}" - location = "${azurerm_resource_group.test.location}" - account_kind = "StorageV2" - account_tier = "Standard" - account_replication_type = "LRS" -} - -resource "azurerm_storage_container" "test" { - name = "test" - resource_group_name = "${azurerm_resource_group.test.name}" - storage_account_name = "${azurerm_storage_account.test.name}" - container_access_type = "%s" -} -`, rInt, location, rString, accessLevel) -} - func testAccAzureRMStorageBlob_update(rInt int, rString, location string) string { template := testAccAzureRMStorageBlob_template(rInt, rString, location, "private") return fmt.Sprintf(` @@ -1322,6 +1297,31 @@ resource "azurerm_storage_container" "test" { `, rInt, location, rString, accessLevel) } +func testAccAzureRMStorageBlob_templateBlockBlobStorage(rInt int, rString, location, accessLevel string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestacc%s" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + account_kind = "StorageV2" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_container" "test" { + name = "test" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" + container_access_type = "%s" +} +`, rInt, location, rString, accessLevel) +} + func testAccAzureRMStorageBlob_templatePremium(rInt int, rString, location, accessLevel string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { From 4e74c6871d887fb67ecb68364f648d4025e82326 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Thu, 5 Sep 2019 11:13:03 +0200 Subject: [PATCH 7/7] r/storage_blob: removing support for defining source_content for Page blobs In retrospect this doesn't make sense since the content needs to be aligned to a 512-byte boundary As such it's more likely this'll be used for Block Blobs rather than Page Blobs --- azurerm/internal/services/storage/blobs.go | 19 +--------- azurerm/resource_arm_storage_blob_test.go | 43 ---------------------- website/docs/r/storage_blob.html.markdown | 6 +-- 3 files changed, 4 insertions(+), 64 deletions(-) diff --git a/azurerm/internal/services/storage/blobs.go b/azurerm/internal/services/storage/blobs.go index 946ea6e96032..ceb220822fe9 100644 --- a/azurerm/internal/services/storage/blobs.go +++ b/azurerm/internal/services/storage/blobs.go @@ -65,9 +65,8 @@ func (sbu BlobUpload) Create(ctx context.Context) error { if sbu.SourceUri != "" { return sbu.copy(ctx) } - if sbu.SourceContent != "" { - return sbu.uploadPageBlobFromContent(ctx) + return fmt.Errorf("`source_content` cannot be specified for a Page blob") } if sbu.Source != "" { return sbu.uploadPageBlob(ctx) @@ -166,22 +165,6 @@ func (sbu BlobUpload) createEmptyPageBlob(ctx context.Context) error { return nil } -func (sbu BlobUpload) uploadPageBlobFromContent(ctx context.Context) error { - tmpFile, err := ioutil.TempFile(os.TempDir(), "upload-") - if err != nil { - return fmt.Errorf("Error creating temporary file: %s", err) - } - defer os.Remove(tmpFile.Name()) - - if _, err = tmpFile.Write([]byte(sbu.SourceContent)); err != nil { - return fmt.Errorf("Error writing Source Content to Temp File: %s", err) - } - defer tmpFile.Close() - - sbu.Source = tmpFile.Name() - return sbu.uploadPageBlob(ctx) -} - func (sbu BlobUpload) uploadPageBlob(ctx context.Context) error { if sbu.Size != 0 { return fmt.Errorf("`size` cannot be set for an uploaded page blob") diff --git a/azurerm/resource_arm_storage_blob_test.go b/azurerm/resource_arm_storage_blob_test.go index 700126f871ed..0b0365646bed 100644 --- a/azurerm/resource_arm_storage_blob_test.go +++ b/azurerm/resource_arm_storage_blob_test.go @@ -509,33 +509,6 @@ func TestAccAzureRMStorageBlob_pageFromExistingBlob(t *testing.T) { }) } -func TestAccAzureRMStorageBlob_pageFromInlineContent(t *testing.T) { - resourceName := "azurerm_storage_blob.test" - ri := tf.AccRandTimeInt() - rs := strings.ToLower(acctest.RandString(11)) - location := testLocation() - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testCheckAzureRMStorageBlobDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAzureRMStorageBlob_pageFromInlineContent(ri, rs, location), - Check: resource.ComposeTestCheckFunc( - testCheckAzureRMStorageBlobExists(resourceName), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"attempts", "parallelism", "size", "source_content", "type"}, - }, - }, - }) -} - func TestAccAzureRMStorageBlob_pageFromLocalFile(t *testing.T) { sourceBlob, err := ioutil.TempFile("", "") if err != nil { @@ -1152,22 +1125,6 @@ resource "azurerm_storage_blob" "test" { `, template) } -func testAccAzureRMStorageBlob_pageFromInlineContent(rInt int, rString, location string) string { - template := testAccAzureRMStorageBlob_template(rInt, rString, location, "private") - return fmt.Sprintf(` -%s - -resource "azurerm_storage_blob" "test" { - name = "rick.morty" - resource_group_name = "${azurerm_resource_group.test.name}" - storage_account_name = "${azurerm_storage_account.test.name}" - storage_container_name = "${azurerm_storage_container.test.name}" - type = "page" - source_content = "Wubba Lubba Dub Dub" -} -`, template) -} - func testAccAzureRMStorageBlob_pageFromLocalBlob(rInt int, rString, location, fileName string) string { template := testAccAzureRMStorageBlob_template(rInt, rString, location, "private") return fmt.Sprintf(` diff --git a/website/docs/r/storage_blob.html.markdown b/website/docs/r/storage_blob.html.markdown index ccaa76ab7815..483e4771a94a 100644 --- a/website/docs/r/storage_blob.html.markdown +++ b/website/docs/r/storage_blob.html.markdown @@ -62,12 +62,12 @@ The following arguments are supported: * `content_type` - (Optional) The content type of the storage blob. Cannot be defined if `source_uri` is defined. Defaults to `application/octet-stream`. -* `source` - (Optional) An absolute path to a file on the local system. Cannot be specified if `source_content` or `source_uri` is specified. +* `source` - (Optional) An absolute path to a file on the local system. This field cannot be specified for Append blobs and annot be specified if `source_content` or `source_uri` is specified. -* `source_content` - (Optional) The content for this blob which should be defined inline. Cannot be specified if `source` or `source_uri` is specified. +* `source_content` - (Optional) The content for this blob which should be defined inline. This field can only be specified for Block blobs and cannot be specified if `source` or `source_uri` is specified. * `source_uri` - (Optional) The URI of an existing blob, or a file in the Azure File service, to use as the source contents - for the blob to be created. Changing this forces a new resource to be created. Cannot be specified if `source` or `source_content` is specified. + for the blob to be created. Changing this forces a new resource to be created. This field cannot be specified for Append blobs and cannot be specified if `source` or `source_content` is specified. * `parallelism` - (Optional) The number of workers per CPU core to run for concurrent uploads. Defaults to `8`.