diff --git a/azurerm/resource_arm_storage_blob.go b/azurerm/resource_arm_storage_blob.go index dffc59b68496..ca40247cae1f 100644 --- a/azurerm/resource_arm_storage_blob.go +++ b/azurerm/resource_arm_storage_blob.go @@ -20,6 +20,7 @@ func resourceArmStorageBlob() *schema.Resource { return &schema.Resource{ Create: resourceArmStorageBlobCreate, Read: resourceArmStorageBlobRead, + Update: resourceArmStorageBlobUpdate, Exists: resourceArmStorageBlobExists, Delete: resourceArmStorageBlobDelete, @@ -53,6 +54,12 @@ func resourceArmStorageBlob() *schema.Resource { Default: 0, ValidateFunc: validateArmStorageBlobSize, }, + "content_type": { + Type: schema.TypeString, + Optional: true, + Default: "application/octet-stream", + ConflictsWith: []string{"source_uri"}, + }, "source": { Type: schema.TypeString, Optional: true, @@ -149,6 +156,7 @@ func resourceArmStorageBlobCreate(d *schema.ResourceData, meta interface{}) erro blobType := d.Get("type").(string) cont := d.Get("storage_container_name").(string) sourceUri := d.Get("source_uri").(string) + contentType := d.Get("content_type").(string) log.Printf("[INFO] Creating blob %q in storage account %q", name, storageAccountName) if sourceUri != "" { @@ -174,7 +182,7 @@ func resourceArmStorageBlobCreate(d *schema.ResourceData, meta interface{}) erro if source != "" { parallelism := d.Get("parallelism").(int) attempts := d.Get("attempts").(int) - if err := resourceArmStorageBlobBlockUploadFromSource(cont, name, source, blobClient, parallelism, attempts); err != nil { + if err := resourceArmStorageBlobBlockUploadFromSource(cont, name, source, contentType, blobClient, parallelism, attempts); err != nil { return fmt.Errorf("Error creating storage blob on Azure: %s", err) } } @@ -183,7 +191,7 @@ func resourceArmStorageBlobCreate(d *schema.ResourceData, meta interface{}) erro if source != "" { parallelism := d.Get("parallelism").(int) attempts := d.Get("attempts").(int) - if err := resourceArmStorageBlobPageUploadFromSource(cont, name, source, blobClient, parallelism, attempts); err != nil { + if err := resourceArmStorageBlobPageUploadFromSource(cont, name, source, contentType, blobClient, parallelism, attempts); err != nil { return fmt.Errorf("Error creating storage blob on Azure: %s", err) } } else { @@ -193,6 +201,7 @@ func resourceArmStorageBlobCreate(d *schema.ResourceData, meta interface{}) erro container := blobClient.GetContainerReference(cont) blob := container.GetBlobReference(name) blob.Properties.ContentLength = size + blob.Properties.ContentType = contentType err := blob.PutPageBlob(options) if err != nil { return fmt.Errorf("Error creating storage blob on Azure: %s", err) @@ -210,7 +219,7 @@ type resourceArmStorageBlobPage struct { section *io.SectionReader } -func resourceArmStorageBlobPageUploadFromSource(container, name, source string, client *storage.BlobStorageClient, parallelism, attempts int) error { +func resourceArmStorageBlobPageUploadFromSource(container, name, source, contentType string, client *storage.BlobStorageClient, parallelism, attempts int) error { workerCount := parallelism * runtime.NumCPU() file, err := os.Open(source) @@ -228,6 +237,7 @@ func resourceArmStorageBlobPageUploadFromSource(container, name, source string, containerRef := client.GetContainerReference(container) blob := containerRef.GetBlobReference(name) blob.Properties.ContentLength = blobSize + blob.Properties.ContentType = contentType err = blob.PutPageBlob(options) if err != nil { return fmt.Errorf("Error creating storage blob on Azure: %s", err) @@ -387,7 +397,7 @@ type resourceArmStorageBlobBlock struct { id string } -func resourceArmStorageBlobBlockUploadFromSource(container, name, source string, client *storage.BlobStorageClient, parallelism, attempts int) error { +func resourceArmStorageBlobBlockUploadFromSource(container, name, source, contentType string, client *storage.BlobStorageClient, parallelism, attempts int) error { workerCount := parallelism * runtime.NumCPU() file, err := os.Open(source) @@ -432,6 +442,7 @@ func resourceArmStorageBlobBlockUploadFromSource(container, name, source string, containerReference := client.GetContainerReference(container) blobReference := containerReference.GetBlobReference(name) + blobReference.Properties.ContentType = contentType options := &storage.PutBlockListOptions{} err = blobReference.PutBlockList(blockList, options) if err != nil { @@ -524,6 +535,40 @@ func resourceArmStorageBlobBlockUploadWorker(ctx resourceArmStorageBlobBlockUplo } } +func resourceArmStorageBlobUpdate(d *schema.ResourceData, meta interface{}) error { + armClient := meta.(*ArmClient) + ctx := armClient.StopContext + + resourceGroupName := d.Get("resource_group_name").(string) + storageAccountName := d.Get("storage_account_name").(string) + + blobClient, accountExists, err := armClient.getBlobStorageClientForStorageAccount(ctx, resourceGroupName, storageAccountName) + if err != nil { + return fmt.Errorf("Error getting storage account %s: %+v", storageAccountName, err) + } + if !accountExists { + return fmt.Errorf("Storage account %s not found in resource group %s", storageAccountName, resourceGroupName) + } + + name := d.Get("name").(string) + storageContainerName := d.Get("storage_container_name").(string) + + container := blobClient.GetContainerReference(storageContainerName) + blob := container.GetBlobReference(name) + + if d.HasChange("content_type") { + blob.Properties.ContentType = d.Get("content_type").(string) + } + + options := &storage.SetBlobPropertiesOptions{} + err = blob.SetProperties(options) + if err != nil { + return fmt.Errorf("Error setting properties of blob %s (container %s, storage account %s): %+v", name, storageContainerName, storageAccountName, err) + } + + return nil +} + func resourceArmStorageBlobRead(d *schema.ResourceData, meta interface{}) error { armClient := meta.(*ArmClient) ctx := armClient.StopContext @@ -556,6 +601,14 @@ func resourceArmStorageBlobRead(d *schema.ResourceData, meta interface{}) error container := blobClient.GetContainerReference(storageContainerName) blob := container.GetBlobReference(name) + + options := &storage.GetBlobPropertiesOptions{} + err = blob.GetProperties(options) + if err != nil { + return fmt.Errorf("Error getting properties of blob %s (container %s, storage account %s): %+v", name, storageContainerName, storageAccountName, err) + } + d.Set("content_type", blob.Properties.ContentType) + url := blob.GetURL() if url == "" { log.Printf("[INFO] URL for %q is empty", name) diff --git a/azurerm/resource_arm_storage_blob_test.go b/azurerm/resource_arm_storage_blob_test.go index 2fea86ff4850..116a70fa7cec 100644 --- a/azurerm/resource_arm_storage_blob_test.go +++ b/azurerm/resource_arm_storage_blob_test.go @@ -314,6 +314,51 @@ func TestAccAzureRMStorageBlob_source_uri(t *testing.T) { }) } +func TestAccAzureRMStorageBlobBlock_blockContentType(t *testing.T) { + resourceName := "azurerm_storage_blob.source" + ri := acctest.RandInt() + rs1 := strings.ToLower(acctest.RandString(11)) + sourceBlob, err := ioutil.TempFile("", "") + if err != nil { + t.Fatalf("Failed to create local source blob file") + } + + _, err = io.CopyN(sourceBlob, rand.Reader, 25*1024*1024) + if err != nil { + t.Fatalf("Failed to write random test to source blob") + } + + err = sourceBlob.Close() + if err != nil { + t.Fatalf("Failed to close source blob") + } + + config := testAccAzureRMStorageBlobPage_blockContentType(ri, rs1, testLocation(), sourceBlob.Name(), "text/plain") + updateConfig := testAccAzureRMStorageBlobPage_blockContentType(ri, rs1, testLocation(), sourceBlob.Name(), "text/vnd.terraform.acctest.tmpfile") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMStorageBlobDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageBlobExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "content_type", "text/plain"), + ), + }, + { + Config: updateConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageBlobExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "content_type", "text/vnd.terraform.acctest.tmpfile"), + ), + }, + }, + }) +} + func testCheckAzureRMStorageBlobExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -666,3 +711,45 @@ resource "azurerm_storage_blob" "destination" { } `, rInt, location, rString, sourceBlobName) } + +func testAccAzureRMStorageBlobPage_blockContentType(rInt int, rString, location string, sourceBlobName, contentType string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "source" { + name = "acctestacc%s" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + account_tier = "Standard" + account_replication_type = "LRS" + + tags { + environment = "staging" + } +} + +resource "azurerm_storage_container" "source" { + name = "source" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.source.name}" + container_access_type = "blob" +} + +resource "azurerm_storage_blob" "source" { + name = "source.vhd" + + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.source.name}" + storage_container_name = "${azurerm_storage_container.source.name}" + + type = "page" + source = "%s" + content_type = "%s" + parallelism = 3 + attempts = 3 +} +`, rInt, location, rString, sourceBlobName, contentType) +} diff --git a/website/docs/r/storage_blob.html.markdown b/website/docs/r/storage_blob.html.markdown index 5e6f7d382153..fcb9a16f67d8 100644 --- a/website/docs/r/storage_blob.html.markdown +++ b/website/docs/r/storage_blob.html.markdown @@ -64,6 +64,8 @@ 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. +* `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_uri` - (Optional) The URI of an existing blob, or a file in the Azure File service, to use as the source contents