From 817022bc2b3ad9d27b2fdd4fab20eaaf104e35c3 Mon Sep 17 00:00:00 2001 From: Federico Feroldi Date: Sat, 13 Jan 2018 08:44:21 +0100 Subject: [PATCH] Issue #691, adds always_on and connection_string attributes to function_app --- azurerm/resource_arm_function_app.go | 168 +++++++++++++- azurerm/resource_arm_function_app_test.go | 266 ++++++++++++++++++++++ website/docs/r/function_app.html.markdown | 17 ++ 3 files changed, 448 insertions(+), 3 deletions(-) diff --git a/azurerm/resource_arm_function_app.go b/azurerm/resource_arm_function_app.go index d16cc294a9f06..7cb7396fb569d 100644 --- a/azurerm/resource_arm_function_app.go +++ b/azurerm/resource_arm_function_app.go @@ -72,6 +72,42 @@ func resourceArmFunctionApp() *schema.Resource { Optional: true, }, + "connection_string": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.APIHub), + string(web.Custom), + string(web.DocDb), + string(web.EventHub), + string(web.MySQL), + string(web.NotificationHub), + string(web.PostgreSQL), + string(web.RedisCache), + string(web.ServiceBus), + string(web.SQLAzure), + string(web.SQLServer), + }, true), + DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + }, + }, + }, + }, + // TODO: (tombuildsstuff) support Update once the API is fixed: // https://github.com/Azure/azure-rest-api-specs/issues/1697 "tags": tagsForceNewSchema(), @@ -80,6 +116,22 @@ func resourceArmFunctionApp() *schema.Resource { Type: schema.TypeString, Computed: true, }, + + "site_config": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "always_on": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + }, + }, + }, }, } } @@ -97,6 +149,8 @@ func resourceArmFunctionAppCreate(d *schema.ResourceData, meta interface{}) erro enabled := d.Get("enabled").(bool) tags := d.Get("tags").(map[string]interface{}) basicAppSettings := getBasicFunctionAppAppSettings(d) + siteConfig := expandFunctionAppSiteConfig(d) + siteConfig.AppSettings = &basicAppSettings siteEnvelope := web.Site{ Kind: &kind, @@ -105,9 +159,7 @@ func resourceArmFunctionAppCreate(d *schema.ResourceData, meta interface{}) erro SiteProperties: &web.SiteProperties{ ServerFarmID: utils.String(appServicePlanID), Enabled: utils.Bool(enabled), - SiteConfig: &web.SiteConfig{ - AppSettings: &basicAppSettings, - }, + SiteConfig: &siteConfig, }, } @@ -157,6 +209,30 @@ func resourceArmFunctionAppUpdate(d *schema.ResourceData, meta interface{}) erro } } + if d.HasChange("site_config") { + siteConfig := expandFunctionAppSiteConfig(d) + siteConfigResource := web.SiteConfigResource{ + SiteConfig: &siteConfig, + } + _, err := client.CreateOrUpdateConfiguration(resGroup, name, siteConfigResource) + if err != nil { + return fmt.Errorf("Error updating Configuration for Function App %q: %+v", name, err) + } + } + + if d.HasChange("connection_string") { + // update the ConnectionStrings + connectionStrings := expandFunctionAppConnectionStrings(d) + properties := web.ConnectionStringDictionary{ + Properties: connectionStrings, + } + + _, err := client.UpdateConnectionStrings(resGroup, name, properties) + if err != nil { + return fmt.Errorf("Error updating Connection Strings for App Service %q: %+v", name, err) + } + } + return resourceArmFunctionAppRead(d, meta) } @@ -186,6 +262,11 @@ func resourceArmFunctionAppRead(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("Error making Read request on AzureRM Function App AppSettings %q: %+v", name, err) } + connectionStringsResp, err := client.ListConnectionStrings(resGroup, name) + if err != nil { + return fmt.Errorf("Error making Read request on AzureRM Function App ConnectionStrings %q: %+v", name, err) + } + d.Set("name", name) d.Set("resource_group_name", resGroup) d.Set("location", azureRMNormalizeLocation(*resp.Location)) @@ -210,6 +291,19 @@ func resourceArmFunctionAppRead(d *schema.ResourceData, meta interface{}) error if err := d.Set("app_settings", appSettings); err != nil { return err } + if err := d.Set("connection_string", flattenFunctionAppConnectionStrings(connectionStringsResp.Properties)); err != nil { + return err + } + + configResp, err := client.GetConfiguration(resGroup, name) + if err != nil { + return fmt.Errorf("Error making Read request on AzureRM Function App Configuration %q: %+v", name, err) + } + + siteConfig := flattenFunctionAppSiteConfig(configResp.SiteConfig) + if err := d.Set("site_config", siteConfig); err != nil { + return err + } flattenAndSetTags(d, resp.Tags) @@ -271,3 +365,71 @@ func expandFunctionAppAppSettings(d *schema.ResourceData) *map[string]*string { return output } + +func expandFunctionAppSiteConfig(d *schema.ResourceData) web.SiteConfig { + configs := d.Get("site_config").([]interface{}) + siteConfig := web.SiteConfig{} + + if len(configs) == 0 { + return siteConfig + } + + config := configs[0].(map[string]interface{}) + + if v, ok := config["always_on"]; ok { + siteConfig.AlwaysOn = utils.Bool(v.(bool)) + } + + return siteConfig +} + +func flattenFunctionAppSiteConfig(input *web.SiteConfig) []interface{} { + results := make([]interface{}, 0) + result := make(map[string]interface{}, 0) + + if input == nil { + log.Printf("[DEBUG] SiteConfig is nil") + return results + } + + if input.AlwaysOn != nil { + result["always_on"] = *input.AlwaysOn + } + + results = append(results, result) + return results +} + +func expandFunctionAppConnectionStrings(d *schema.ResourceData) *map[string]*web.ConnStringValueTypePair { + input := d.Get("connection_string").([]interface{}) + output := make(map[string]*web.ConnStringValueTypePair, len(input)) + + for _, v := range input { + vals := v.(map[string]interface{}) + + csName := vals["name"].(string) + csType := vals["type"].(string) + csValue := vals["value"].(string) + + output[csName] = &web.ConnStringValueTypePair{ + Value: utils.String(csValue), + Type: web.ConnectionStringType(csType), + } + } + + return &output +} + +func flattenFunctionAppConnectionStrings(input *map[string]*web.ConnStringValueTypePair) interface{} { + results := make([]interface{}, 0) + + for k, v := range *input { + result := make(map[string]interface{}, 0) + result["name"] = k + result["type"] = string(v.Type) + result["value"] = *v.Value + results = append(results, result) + } + + return results +} diff --git a/azurerm/resource_arm_function_app_test.go b/azurerm/resource_arm_function_app_test.go index 751da2c73c3ed..787af161364bc 100644 --- a/azurerm/resource_arm_function_app_test.go +++ b/azurerm/resource_arm_function_app_test.go @@ -94,6 +94,106 @@ func TestAccAzureRMFunctionApp_appSettings(t *testing.T) { }) } +func TestAccAzureRMFunctionApp_siteConfig(t *testing.T) { + resourceName := "azurerm_function_app.test" + ri := acctest.RandInt() + rs := strings.ToLower(acctest.RandString(11)) + config := testAccAzureRMFunctionApp_alwaysOn(ri, rs, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMFunctionAppDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "site_config.0.always_on", "true"), + ), + }, + }, + }) +} + +func TestAccAzureRMFunctionApp_connectionStrings(t *testing.T) { + resourceName := "azurerm_function_app.test" + ri := acctest.RandInt() + rs := strings.ToLower(acctest.RandString(11)) + config := testAccAzureRMFunctionApp_connectionStrings(ri, rs, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMFunctionAppDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "connection_string.0.name", "Example"), + resource.TestCheckResourceAttr(resourceName, "connection_string.0.value", "some-postgresql-connection-string"), + resource.TestCheckResourceAttr(resourceName, "connection_string.0.type", "PostgreSQL"), + ), + }, + }, + }) +} + +func TestAccAzureRMFunctionApp_siteConfigMulti(t *testing.T) { + resourceName := "azurerm_function_app.test" + ri := acctest.RandInt() + rs := strings.ToLower(acctest.RandString(11)) + configBase := testAccAzureRMFunctionApp_basic(ri, rs, testLocation()) + configUpdate1 := testAccAzureRMFunctionApp_appSettings(ri, rs, testLocation()) + configUpdate2 := testAccAzureRMFunctionApp_appSettingsAlwaysOn(ri, rs, testLocation()) + configUpdate3 := testAccAzureRMFunctionApp_appSettingsAlwaysOnConnectionStrings(ri, rs, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMFunctionAppDestroy, + Steps: []resource.TestStep{ + { + Config: configBase, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "app_settings.%", "0"), + ), + }, + { + Config: configUpdate1, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "app_settings.%", "1"), + resource.TestCheckResourceAttr(resourceName, "app_settings.hello", "world"), + ), + }, + { + Config: configUpdate2, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "app_settings.%", "1"), + resource.TestCheckResourceAttr(resourceName, "app_settings.hello", "world"), + resource.TestCheckResourceAttr(resourceName, "site_config.0.always_on", "true"), + ), + }, + { + Config: configUpdate3, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "app_settings.%", "1"), + resource.TestCheckResourceAttr(resourceName, "app_settings.hello", "world"), + resource.TestCheckResourceAttr(resourceName, "site_config.0.always_on", "true"), + resource.TestCheckResourceAttr(resourceName, "connection_string.0.name", "Example"), + resource.TestCheckResourceAttr(resourceName, "connection_string.0.value", "some-postgresql-connection-string"), + resource.TestCheckResourceAttr(resourceName, "connection_string.0.type", "PostgreSQL"), + ), + }, + }, + }) +} + func TestAccAzureRMFunctionApp_updateVersion(t *testing.T) { resourceName := "azurerm_function_app.test" ri := acctest.RandInt() @@ -323,3 +423,169 @@ resource "azurerm_function_app" "test" { } `, rInt, location, rString) } + +func testAccAzureRMFunctionApp_alwaysOn(rInt int, rString, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_function_app" "test" { + name = "acctest-%[1]d-func" + 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_connection_string = "${azurerm_storage_account.test.primary_connection_string}" + site_config { + always_on = true + } +} +`, rInt, location, rString) +} + +func testAccAzureRMFunctionApp_connectionStrings(rInt int, rString, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_function_app" "test" { + name = "acctest-%[1]d-func" + 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_connection_string = "${azurerm_storage_account.test.primary_connection_string}" + + connection_string { + name = "Example" + value = "some-postgresql-connection-string" + type = "PostgreSQL" + } +} +`, rInt, location, rString) +} + +func testAccAzureRMFunctionApp_appSettingsAlwaysOn(rInt int, rString, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_function_app" "test" { + name = "acctest-%[1]d-func" + 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_connection_string = "${azurerm_storage_account.test.primary_connection_string}" + app_settings { + "hello" = "world" + } + site_config { + always_on = true + } +} +`, rInt, location, rString) +} + +func testAccAzureRMFunctionApp_appSettingsAlwaysOnConnectionStrings(rInt int, rString, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_function_app" "test" { + name = "acctest-%[1]d-func" + 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_connection_string = "${azurerm_storage_account.test.primary_connection_string}" + app_settings { + "hello" = "world" + } + site_config { + always_on = true + } + connection_string { + name = "Example" + value = "some-postgresql-connection-string" + type = "PostgreSQL" + } +} +`, rInt, location, rString) +} diff --git a/website/docs/r/function_app.html.markdown b/website/docs/r/function_app.html.markdown index 42ce035020cda..5dc6b21ff928a 100644 --- a/website/docs/r/function_app.html.markdown +++ b/website/docs/r/function_app.html.markdown @@ -65,12 +65,29 @@ The following arguments are supported: * `app_settings` - (Optional) A key-value pair of App Settings. +* `connection_string` - (Optional) An `connection_string` block as defined below. + * `enabled` - (Optional) Is the Function App enabled? Changing this forces a new resource to be created. * `version` - (Optional) The runtime version associated with the Function App. Possible values are `~1` and `beta`. Defaults to `~1`. +* `site_config` - (Optional) A `site_config` object as defined below. + * `tags` - (Optional) A mapping of tags to assign to the resource. Changing this forces a new resource to be created. +--- + +`connection_string` supports the following: + +* `name` - (Required) The name of the Connection String. +* `type` - (Required) The type of the Connection String. Possible values are `APIHub`, `Custom`, `DocDb`, `EventHub`, `MySQL`, `NotificationHub`, `PostgreSQL`, `RedisCache`, `ServiceBus`, `SQLAzure` and `SQLServer`. +* `value` - (Required) The value for the Connection String. + +--- + +`site_config` supports the following: + +* `always_on` - (Optional) Should the app be loaded at all times? Defaults to `false`. ## Attributes Reference