From eb5a1f299b2946d2453e10e8a46658edaf13f4f2 Mon Sep 17 00:00:00 2001 From: Joakim Bakke Hellum Date: Thu, 4 Jul 2019 23:53:42 +0200 Subject: [PATCH] azurerm_app_service: support for storage mounts --- azurerm/helpers/azure/app_service.go | 101 ++++++++++++++++ azurerm/resource_arm_app_service.go | 22 ++++ azurerm/resource_arm_app_service_test.go | 148 +++++++++++++++++++++++ website/docs/r/app_service.html.markdown | 18 +++ 4 files changed, 289 insertions(+) diff --git a/azurerm/helpers/azure/app_service.go b/azurerm/helpers/azure/app_service.go index ba7729eed675..44e49775a156 100644 --- a/azurerm/helpers/azure/app_service.go +++ b/azurerm/helpers/azure/app_service.go @@ -503,6 +503,56 @@ func SchemaAppServiceLogsConfig() *schema.Schema { } } +func SchemaAppServiceStorageAccounts() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.AzureBlob), + string(web.AzureFiles), + }, false), + }, + + "account_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "share_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "access_key": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "mount_path": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + } +} + func SchemaAppServiceDataSourceSiteConfig() *schema.Schema { return &schema.Schema{ Type: schema.TypeList, @@ -1400,3 +1450,54 @@ func FlattenAppServiceSiteConfig(input *web.SiteConfig) []interface{} { return append(results, result) } + +func ExpandAppServiceStorageAccounts(d *schema.ResourceData) map[string]*web.AzureStorageInfoValue { + input := d.Get("storage_account").(*schema.Set).List() + output := make(map[string]*web.AzureStorageInfoValue, len(input)) + + for _, v := range input { + vals := v.(map[string]interface{}) + + saName := vals["name"].(string) + saType := vals["type"].(string) + saAccountName := vals["account_name"].(string) + saShareName := vals["share_name"].(string) + saAccessKey := vals["access_key"].(string) + saMountPath := vals["mount_path"].(string) + + output[saName] = &web.AzureStorageInfoValue{ + Type: web.AzureStorageType(saType), + AccountName: utils.String(saAccountName), + ShareName: utils.String(saShareName), + AccessKey: utils.String(saAccessKey), + MountPath: utils.String(saMountPath), + } + } + + return output +} + +func FlattenAppServiceStorageAccounts(input map[string]*web.AzureStorageInfoValue) []interface{} { + results := make([]interface{}, 0) + + for k, v := range input { + result := make(map[string]interface{}) + result["name"] = k + result["type"] = string(v.Type) + if v.AccountName != nil { + result["account_name"] = *v.AccountName + } + if v.ShareName != nil { + result["share_name"] = *v.ShareName + } + if v.AccessKey != nil { + result["access_key"] = *v.AccessKey + } + if v.MountPath != nil { + result["mount_path"] = *v.MountPath + } + results = append(results, result) + } + + return results +} diff --git a/azurerm/resource_arm_app_service.go b/azurerm/resource_arm_app_service.go index 788658bb8fbc..fe0edf48692f 100644 --- a/azurerm/resource_arm_app_service.go +++ b/azurerm/resource_arm_app_service.go @@ -102,6 +102,8 @@ func resourceArmAppService() *schema.Resource { Computed: true, }, + "storage_account": azure.SchemaAppServiceStorageAccounts(), + "connection_string": { Type: schema.TypeSet, Optional: true, @@ -418,6 +420,17 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error } } + if d.HasChange("storage_account") { + storageAccounts := azure.ExpandAppServiceStorageAccounts(d) + properties := web.AzureStoragePropertyDictionaryResource{ + Properties: storageAccounts, + } + + if _, err := client.UpdateAzureStorageAccounts(ctx, resGroup, name, properties); err != nil { + return fmt.Errorf("Error updating Storage Accounts for App Service %q: %+v", name, err) + } + } + if d.HasChange("connection_string") { // update the ConnectionStrings connectionStrings := expandAppServiceConnectionStrings(d) @@ -505,6 +518,11 @@ func resourceArmAppServiceRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error making Read request on AzureRM App Service AppSettings %q: %+v", name, err) } + storageAccountsResp, err := client.ListAzureStorageAccounts(ctx, resGroup, name) + if err != nil { + return fmt.Errorf("Error making Read request on AzureRM App Service Storage Accounts %q: %+v", name, err) + } + connectionStringsResp, err := client.ListConnectionStrings(ctx, resGroup, name) if err != nil { return fmt.Errorf("Error making Read request on AzureRM App Service ConnectionStrings %q: %+v", name, err) @@ -555,6 +573,10 @@ func resourceArmAppServiceRead(d *schema.ResourceData, meta interface{}) error { return err } + if err := d.Set("storage_account", azure.FlattenAppServiceStorageAccounts(storageAccountsResp.Properties)); err != nil { + return err + } + if err := d.Set("connection_string", flattenAppServiceConnectionStrings(connectionStringsResp.Properties)); err != nil { return err } diff --git a/azurerm/resource_arm_app_service_test.go b/azurerm/resource_arm_app_service_test.go index b4c2b5a835cf..6ff0da0099d1 100644 --- a/azurerm/resource_arm_app_service_test.go +++ b/azurerm/resource_arm_app_service_test.go @@ -600,6 +600,39 @@ func TestAccAzureRMAppService_connectionStrings(t *testing.T) { }) } +func TestAccAzureRMAppService_storageAccounts(t *testing.T) { + resourceName := "azurerm_app_service.test" + ri := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAppServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAppService_storageAccounts(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServiceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "storage_account.#", "1"), + ), + }, + { + Config: testAccAzureRMAppService_storageAccountsUpdated(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServiceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "storage_account.#", "2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccAzureRMAppService_oneIpRestriction(t *testing.T) { resourceName := "azurerm_app_service.test" ri := tf.AccRandTimeInt() @@ -2454,6 +2487,121 @@ resource "azurerm_app_service" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMAppService_storageAccounts(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acct%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_container" "test" { + name = "acctestcontainer" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_app_service" "test" { + name = "acctestAS-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + app_service_plan_id = "${azurerm_app_service_plan.test.id}" + + storage_account { + name = "blobs" + type = "AzureBlob" + account_name = "${azurerm_storage_account.test.name}" + share_name = "${azurerm_storage_container.test.name}" + access_key = "${azurerm_storage_account.test.primary_access_key}" + mount_path = "/blobs" + } +} +`, rInt, location, rInt, rInt, rInt) +} + +func testAccAzureRMAppService_storageAccountsUpdated(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_storage_account" "test" { + name = "acct%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_container" "test" { + name = "acctestcontainer" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" +} + +resource "azurerm_storage_share" "test" { + name = "acctestshare" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" +} + +resource "azurerm_app_service" "test" { + name = "acctestAS-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + app_service_plan_id = "${azurerm_app_service_plan.test.id}" + + storage_account { + name = "blobs" + type = "AzureBlob" + account_name = "${azurerm_storage_account.test.name}" + share_name = "${azurerm_storage_container.test.name}" + access_key = "${azurerm_storage_account.test.primary_access_key}" + mount_path = "/blobs" + } + + storage_account { + name = "files" + type = "AzureFiles" + account_name = "${azurerm_storage_account.test.name}" + share_name = "${azurerm_storage_share.test.name}" + access_key = "${azurerm_storage_account.test.primary_access_key}" + mount_path = "/files" + } +} +`, rInt, location, rInt, rInt, rInt) +} + func testAccAzureRMAppService_oneIpRestriction(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/website/docs/r/app_service.html.markdown b/website/docs/r/app_service.html.markdown index bdeb19b653fa..8ee41a678902 100644 --- a/website/docs/r/app_service.html.markdown +++ b/website/docs/r/app_service.html.markdown @@ -73,6 +73,8 @@ The following arguments are supported: * `auth_settings` - (Optional) A `auth_settings` block as defined below. +* `storage_account` - (Optional) One or more `storage_account` blocks as defined below. + * `connection_string` - (Optional) One or more `connection_string` blocks as defined below. * `client_affinity_enabled` - (Optional) Should the App Service send session affinity cookies, which route client requests in the same session to the same instance? @@ -93,6 +95,22 @@ The following arguments are supported: --- +A `storage_account` block supports the following: + +* `name` - (Required) The name of the storage account identifier. + +* `type` - (Required) The type of storage. Possible values are `AzureBlob` and `AzureFiles`. + +* `account_name` - (Required) The name of the storage account. + +* `share_name` - (Required) The name of the file share (container name, for Blob storage). + +* `access_key` - (Required) The access key for the storage account. + +* `mount_path` - (Optional) The path to mount the storage within the site's runtime environment. + +--- + A `connection_string` block supports the following: * `name` - (Required) The name of the Connection String.