From dc574660e78987118a926d36757b4018ee9a9a7c Mon Sep 17 00:00:00 2001 From: Roeland Nieuwenhuis Date: Sat, 7 Jul 2018 21:31:50 +0200 Subject: [PATCH 1/5] Do not load WEBSITE_CONTENT app settings when on a consumption plan --- azurerm/resource_arm_function_app.go | 55 ++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/azurerm/resource_arm_function_app.go b/azurerm/resource_arm_function_app.go index 68471cb57f84..6803444b83ce 100644 --- a/azurerm/resource_arm_function_app.go +++ b/azurerm/resource_arm_function_app.go @@ -1,8 +1,10 @@ package azurerm import ( + "context" "fmt" "log" + "strings" "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2016-09-01/web" "github.com/hashicorp/terraform/helper/schema" @@ -214,7 +216,14 @@ func resourceArmFunctionAppCreate(d *schema.ResourceData, meta interface{}) erro clientAffinityEnabled := d.Get("client_affinity_enabled").(bool) httpsOnly := d.Get("https_only").(bool) tags := d.Get("tags").(map[string]interface{}) - basicAppSettings := getBasicFunctionAppAppSettings(d) + appServiceTier, err := getFunctionAppServiceTier(ctx, appServicePlanID, meta) + + if err != nil { + return err + } + + basicAppSettings := getBasicFunctionAppAppSettings(d, appServiceTier) + siteConfig := expandFunctionAppSiteConfig(d) siteConfig.AppSettings = &basicAppSettings @@ -279,7 +288,13 @@ func resourceArmFunctionAppUpdate(d *schema.ResourceData, meta interface{}) erro clientAffinityEnabled := d.Get("client_affinity_enabled").(bool) httpsOnly := d.Get("https_only").(bool) tags := d.Get("tags").(map[string]interface{}) - basicAppSettings := getBasicFunctionAppAppSettings(d) + + appServiceTier, err := getFunctionAppServiceTier(ctx, appServicePlanID, meta) + + if err != nil { + return err + } + basicAppSettings := getBasicFunctionAppAppSettings(d, appServiceTier) siteConfig := expandFunctionAppSiteConfig(d) siteConfig.AppSettings = &basicAppSettings @@ -312,7 +327,7 @@ func resourceArmFunctionAppUpdate(d *schema.ResourceData, meta interface{}) erro return err } - appSettings := expandFunctionAppAppSettings(d) + appSettings := expandFunctionAppAppSettings(d, appServiceTier) settings := web.StringDictionary{ Properties: appSettings, } @@ -458,7 +473,7 @@ func resourceArmFunctionAppDelete(d *schema.ResourceData, meta interface{}) erro return nil } -func getBasicFunctionAppAppSettings(d *schema.ResourceData) []web.NameValuePair { +func getBasicFunctionAppAppSettings(d *schema.ResourceData, appServiceTier string) []web.NameValuePair { dashboardPropName := "AzureWebJobsDashboard" storagePropName := "AzureWebJobsStorage" functionVersionPropName := "FUNCTIONS_EXTENSION_VERSION" @@ -469,19 +484,45 @@ func getBasicFunctionAppAppSettings(d *schema.ResourceData) []web.NameValuePair functionVersion := d.Get("version").(string) contentShare := d.Get("name").(string) + "-content" - return []web.NameValuePair{ + basicSettings := []web.NameValuePair{ {Name: &dashboardPropName, Value: &storageConnection}, {Name: &storagePropName, Value: &storageConnection}, {Name: &functionVersionPropName, Value: &functionVersion}, + } + + consumptionSettings := []web.NameValuePair{ {Name: &contentSharePropName, Value: &contentShare}, {Name: &contentFileConnStringPropName, Value: &storageConnection}, } + + // If the application plan is dynamic (consumption), we do not want to include WEBSITE_CONTENT components + if strings.EqualFold(appServiceTier, "dynamic") { + return basicSettings + } + return append(basicSettings, consumptionSettings...) +} + +func getFunctionAppServiceTier(ctx context.Context, appServicePlanId string, meta interface{}) (string, error) { + id, err := parseAzureResourceID(appServicePlanId) + if err != nil { + return "", fmt.Errorf("[ERROR] Unable to parse App Service Plan ID '%s': %+v", appServicePlanId, err) + } + + log.Printf("[DEBUG] Retrieving App Server Plan %s", id.Path["serverfarms"]) + + appServicePlansClient := meta.(*ArmClient).appServicePlansClient + appServicePlan, err := appServicePlansClient.Get(ctx, id.ResourceGroup, id.Path["serverfarms"]) + if err != nil { + return "", fmt.Errorf("[ERROR] Could not retrieve App Service Plan ID '%s': %+v", appServicePlanId, err) + } + + return *appServicePlan.Sku.Tier, nil } -func expandFunctionAppAppSettings(d *schema.ResourceData) map[string]*string { +func expandFunctionAppAppSettings(d *schema.ResourceData, appServiceTier string) map[string]*string { output := expandAppServiceAppSettings(d) - basicAppSettings := getBasicFunctionAppAppSettings(d) + basicAppSettings := getBasicFunctionAppAppSettings(d, appServiceTier) for _, p := range basicAppSettings { output[*p.Name] = p.Value } From cb19e80520a262a95feee0753e3a5338d201f474 Mon Sep 17 00:00:00 2001 From: Roeland Nieuwenhuis Date: Wed, 11 Jul 2018 21:18:08 +0200 Subject: [PATCH 2/5] Inverse the dynamic plan check, thanks @APErebus --- azurerm/resource_arm_function_app.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azurerm/resource_arm_function_app.go b/azurerm/resource_arm_function_app.go index 6803444b83ce..da4e371a9cc3 100644 --- a/azurerm/resource_arm_function_app.go +++ b/azurerm/resource_arm_function_app.go @@ -495,8 +495,8 @@ func getBasicFunctionAppAppSettings(d *schema.ResourceData, appServiceTier strin {Name: &contentFileConnStringPropName, Value: &storageConnection}, } - // If the application plan is dynamic (consumption), we do not want to include WEBSITE_CONTENT components - if strings.EqualFold(appServiceTier, "dynamic") { + // If the application plan is NOT dynamic (consumption plan), we do NOT want to include WEBSITE_CONTENT components + if !strings.EqualFold(appServiceTier, "dynamic") { return basicSettings } return append(basicSettings, consumptionSettings...) From c71f67445f0eada5e43f9afbd7f7441abbf042e0 Mon Sep 17 00:00:00 2001 From: Roeland Nieuwenhuis Date: Wed, 11 Jul 2018 21:23:45 +0200 Subject: [PATCH 3/5] '%s' -> %q for safety --- azurerm/resource_arm_function_app.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azurerm/resource_arm_function_app.go b/azurerm/resource_arm_function_app.go index da4e371a9cc3..b303dd5c604e 100644 --- a/azurerm/resource_arm_function_app.go +++ b/azurerm/resource_arm_function_app.go @@ -505,7 +505,7 @@ func getBasicFunctionAppAppSettings(d *schema.ResourceData, appServiceTier strin func getFunctionAppServiceTier(ctx context.Context, appServicePlanId string, meta interface{}) (string, error) { id, err := parseAzureResourceID(appServicePlanId) if err != nil { - return "", fmt.Errorf("[ERROR] Unable to parse App Service Plan ID '%s': %+v", appServicePlanId, err) + return "", fmt.Errorf("[ERROR] Unable to parse App Service Plan ID %q: %+v", appServicePlanId, err) } log.Printf("[DEBUG] Retrieving App Server Plan %s", id.Path["serverfarms"]) @@ -513,7 +513,7 @@ func getFunctionAppServiceTier(ctx context.Context, appServicePlanId string, met appServicePlansClient := meta.(*ArmClient).appServicePlansClient appServicePlan, err := appServicePlansClient.Get(ctx, id.ResourceGroup, id.Path["serverfarms"]) if err != nil { - return "", fmt.Errorf("[ERROR] Could not retrieve App Service Plan ID '%s': %+v", appServicePlanId, err) + return "", fmt.Errorf("[ERROR] Could not retrieve App Service Plan ID %q: %+v", appServicePlanId, err) } return *appServicePlan.Sku.Tier, nil From b6831495038974863d00e20ac479e35cd62d098f Mon Sep 17 00:00:00 2001 From: Roeland Nieuwenhuis Date: Wed, 11 Jul 2018 21:54:35 +0200 Subject: [PATCH 4/5] Add a nil check --- azurerm/resource_arm_function_app.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/azurerm/resource_arm_function_app.go b/azurerm/resource_arm_function_app.go index b303dd5c604e..8417777ee0be 100644 --- a/azurerm/resource_arm_function_app.go +++ b/azurerm/resource_arm_function_app.go @@ -516,7 +516,12 @@ func getFunctionAppServiceTier(ctx context.Context, appServicePlanId string, met return "", fmt.Errorf("[ERROR] Could not retrieve App Service Plan ID %q: %+v", appServicePlanId, err) } - return *appServicePlan.Sku.Tier, nil + if sku := appServicePlan.Sku; sku != nil { + if tier := sku.Tier; tier != nil { + return *tier, nil + } + } + return "", fmt.Errorf("No `sku` block was returned for App Service Plan ID %q", appServicePlanId) } func expandFunctionAppAppSettings(d *schema.ResourceData, appServiceTier string) map[string]*string { From 342665dacd947321e4230e3ef07bb2206770d19e Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Mon, 16 Jul 2018 14:06:52 +0200 Subject: [PATCH 5/5] Added tests to ensure the settings are removed: ``` $ acctests azurerm TestAccAzureRMFunctionApp_basic === RUN TestAccAzureRMFunctionApp_basic --- PASS: TestAccAzureRMFunctionApp_basic (111.90s) PASS ok github.com/terraform-providers/terraform-provider-azurerm/azurerm 111.938s ``` ``` $ acctests azurerm TestAccAzureRMFunctionApp_consumptionPlan === RUN TestAccAzureRMFunctionApp_consumptionPlan --- PASS: TestAccAzureRMFunctionApp_consumptionPlan (143.36s) PASS ok github.com/terraform-providers/terraform-provider-azurerm/azurerm 143.406s ``` --- azurerm/resource_arm_function_app.go | 1 - azurerm/resource_arm_function_app_test.go | 66 +++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/azurerm/resource_arm_function_app.go b/azurerm/resource_arm_function_app.go index 8417777ee0be..047f6230e43d 100644 --- a/azurerm/resource_arm_function_app.go +++ b/azurerm/resource_arm_function_app.go @@ -217,7 +217,6 @@ func resourceArmFunctionAppCreate(d *schema.ResourceData, meta interface{}) erro httpsOnly := d.Get("https_only").(bool) tags := d.Get("tags").(map[string]interface{}) appServiceTier, err := getFunctionAppServiceTier(ctx, appServicePlanID, meta) - if err != nil { return err } diff --git a/azurerm/resource_arm_function_app_test.go b/azurerm/resource_arm_function_app_test.go index ed7c395b6101..2fb64baae723 100644 --- a/azurerm/resource_arm_function_app_test.go +++ b/azurerm/resource_arm_function_app_test.go @@ -27,6 +27,7 @@ func TestAccAzureRMFunctionApp_basic(t *testing.T) { Config: config, Check: resource.ComposeTestCheckFunc( testCheckAzureRMFunctionAppExists(resourceName), + testCheckAzureRMFunctionAppHasNoContentShare(resourceName), resource.TestCheckResourceAttr(resourceName, "version", "~1"), ), }, @@ -328,6 +329,7 @@ func TestAccAzureRMFunctionApp_consumptionPlan(t *testing.T) { Config: config, Check: resource.ComposeTestCheckFunc( testCheckAzureRMFunctionAppExists(resourceName), + testCheckAzureRMFunctionAppHasContentShare(resourceName), resource.TestCheckResourceAttr(resourceName, "site_config.0.use_32_bit_worker_process", "true"), ), }, @@ -453,6 +455,70 @@ func testCheckAzureRMFunctionAppExists(name string) resource.TestCheckFunc { } } +func testCheckAzureRMFunctionAppHasContentShare(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + functionAppName := rs.Primary.Attributes["name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for Function App: %s", functionAppName) + } + + client := testAccProvider.Meta().(*ArmClient).appServicesClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + appSettingsResp, err := client.ListApplicationSettings(ctx, resourceGroup, functionAppName) + if err != nil { + return fmt.Errorf("Error making Read request on AzureRM Function App AppSettings %q: %+v", functionAppName, err) + } + + for k, _ := range appSettingsResp.Properties { + if strings.EqualFold("WEBSITE_CONTENTSHARE", k) { + return nil + } + } + + return fmt.Errorf("Function App %q does not contain the Website Content Share!", functionAppName) + } +} + +func testCheckAzureRMFunctionAppHasNoContentShare(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + functionAppName := rs.Primary.Attributes["name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for Function App: %s", functionAppName) + } + + client := testAccProvider.Meta().(*ArmClient).appServicesClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + appSettingsResp, err := client.ListApplicationSettings(ctx, resourceGroup, functionAppName) + if err != nil { + return fmt.Errorf("Error making Read request on AzureRM Function App AppSettings %q: %+v", functionAppName, err) + } + + for k, v := range appSettingsResp.Properties { + if strings.EqualFold("WEBSITE_CONTENTSHARE", k) && v != nil && *v != "" { + return fmt.Errorf("Function App %q contains the Website Content Share!", functionAppName) + } + } + + return nil + } +} + func testAccAzureRMFunctionApp_basic(rInt int, storage string, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" {