diff --git a/azurerm/data_source_app_service.go b/azurerm/data_source_app_service.go index 445306507990..5977abbbd9ff 100644 --- a/azurerm/data_source_app_service.go +++ b/azurerm/data_source_app_service.go @@ -28,7 +28,7 @@ func dataSourceArmAppService() *schema.Resource { Computed: true, }, - "site_config": azure.SchemaAppServiceSiteConfig(), + "site_config": azure.SchemaAppServiceDataSourceSiteConfig(), "client_affinity_enabled": { Type: schema.TypeBool, diff --git a/azurerm/helpers/azure/app_service.go b/azurerm/helpers/azure/app_service.go index 5751ad9acb6b..16c2cbb66fce 100644 --- a/azurerm/helpers/azure/app_service.go +++ b/azurerm/helpers/azure/app_service.go @@ -222,11 +222,235 @@ func SchemaAppServiceSiteConfig() *schema.Schema { Type: schema.TypeString, Optional: true, }, + + "cors": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "allowed_origins": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "support_credentials": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + }, + }, + }, + }, + }, + } +} + +func SchemaAppServiceDataSourceSiteConfig() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "always_on": { + Type: schema.TypeBool, + Computed: true, + }, + + "app_command_line": { + Type: schema.TypeString, + Computed: true, + }, + + "default_documents": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "dotnet_framework_version": { + Type: schema.TypeString, + Computed: true, + }, + + "http2_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + + "ip_restriction": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip_address": { + Type: schema.TypeString, + Computed: true, + }, + "subnet_mask": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "java_version": { + Type: schema.TypeString, + Computed: true, + }, + + "java_container": { + Type: schema.TypeString, + Computed: true, + }, + + "java_container_version": { + Type: schema.TypeString, + Computed: true, + }, + + "local_mysql_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + + "managed_pipeline_mode": { + Type: schema.TypeString, + Computed: true, + }, + + "php_version": { + Type: schema.TypeString, + Computed: true, + }, + + "python_version": { + Type: schema.TypeString, + Computed: true, + }, + + "remote_debugging_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + + "remote_debugging_version": { + Type: schema.TypeString, + Computed: true, + }, + + "scm_type": { + Type: schema.TypeString, + Computed: true, + }, + + "use_32_bit_worker_process": { + Type: schema.TypeBool, + Computed: true, + }, + + "websockets_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + + "ftps_state": { + Type: schema.TypeString, + Computed: true, + }, + + "linux_fx_version": { + Type: schema.TypeString, + Computed: true, + }, + + "min_tls_version": { + Type: schema.TypeString, + Computed: true, + }, + + "virtual_network_name": { + Type: schema.TypeString, + Computed: true, + }, + + "cors": { + Type: schema.TypeList, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "allowed_origins": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "support_credentials": { + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + }, }, }, } } +func ExpandAppServiceCorsSettings(input interface{}) web.CorsSettings { + settings := input.([]interface{}) + corsSettings := web.CorsSettings{} + + if len(settings) == 0 { + return corsSettings + } + + setting := settings[0].(map[string]interface{}) + + if v, ok := setting["allowed_origins"]; ok { + input := v.(*schema.Set).List() + + allowedOrigins := make([]string, 0) + for _, param := range input { + allowedOrigins = append(allowedOrigins, param.(string)) + } + + corsSettings.AllowedOrigins = &allowedOrigins + } + + if v, ok := setting["support_credentials"]; ok { + corsSettings.SupportCredentials = utils.Bool(v.(bool)) + } + + return corsSettings +} + +func FlattenAppServiceCorsSettings(input *web.CorsSettings) []interface{} { + results := make([]interface{}, 0) + if input == nil { + return results + } + + result := make(map[string]interface{}) + + allowedOrigins := make([]interface{}, 0) + if s := input.AllowedOrigins; s != nil { + for _, v := range *s { + allowedOrigins = append(allowedOrigins, v) + } + } + result["allowed_origins"] = schema.NewSet(schema.HashString, allowedOrigins) + + if input.SupportCredentials != nil { + result["support_credentials"] = *input.SupportCredentials + } + + return append(results, result) +} + func ExpandAppServiceSiteConfig(input interface{}) web.SiteConfig { configs := input.([]interface{}) siteConfig := web.SiteConfig{} @@ -355,6 +579,12 @@ func ExpandAppServiceSiteConfig(input interface{}) web.SiteConfig { siteConfig.VnetName = utils.String(v.(string)) } + if v, ok := config["cors"]; ok { + corsSettings := v.(interface{}) + expand := ExpandAppServiceCorsSettings(corsSettings) + siteConfig.Cors = &expand + } + return siteConfig } @@ -466,5 +696,7 @@ func FlattenAppServiceSiteConfig(input *web.SiteConfig) []interface{} { result["ftps_state"] = string(input.FtpsState) result["min_tls_version"] = string(input.MinTLSVersion) + result["cors"] = FlattenAppServiceCorsSettings(input.Cors) + return append(results, result) } diff --git a/azurerm/resource_arm_app_service_slot_test.go b/azurerm/resource_arm_app_service_slot_test.go index e358e0f7d5e7..4bb9df69b550 100644 --- a/azurerm/resource_arm_app_service_slot_test.go +++ b/azurerm/resource_arm_app_service_slot_test.go @@ -258,6 +258,30 @@ func TestAccAzureRMAppServiceSlot_connectionStrings(t *testing.T) { }) } +func TestAccAzureRMAppServiceSlot_corsSettings(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: testCheckAzureRMAppServiceSlotDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAppServiceSlot_corsSettings(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServiceExists(resourceName), + )}, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccAzureRMAppServiceSlot_defaultDocuments(t *testing.T) { resourceName := "azurerm_app_service_slot.test" ri := tf.AccRandTimeInt() @@ -1251,6 +1275,52 @@ resource "azurerm_app_service_slot" "test" { `, rInt, location, rInt, rInt, rInt) } +func testAccAzureRMAppServiceSlot_corsSettings(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_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}" +} + +resource "azurerm_app_service_slot" "test" { + name = "acctestASSlot-%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}" + app_service_name = "${azurerm_app_service.test.name}" + + site_config { + cors { + allowed_origins = [ + "http://www.contoso.com", + "www.contoso.com", + "contoso.com" + ] + support_credentials = true + } + } +} +`, rInt, location, rInt, rInt, rInt) +} + func testAccAzureRMAppServiceSlot_defaultDocuments(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_app_service_test.go b/azurerm/resource_arm_app_service_test.go index 8aec9e76f477..993bd3b66c70 100644 --- a/azurerm/resource_arm_app_service_test.go +++ b/azurerm/resource_arm_app_service_test.go @@ -1208,6 +1208,33 @@ func TestAccAzureRMAppService_minTls(t *testing.T) { }) } +func TestAccAzureRMAppService_corsSettings(t *testing.T) { + resourceName := "azurerm_app_service.test" + ri := tf.AccRandTimeInt() + config := testAccAzureRMAppService_corsSettings(ri, testLocation()) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAppServiceDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServiceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "site_config.0.cors.#", "1"), + resource.TestCheckResourceAttr(resourceName, "site_config.0.cors.0.support_credentials", "true"), + resource.TestCheckResourceAttr(resourceName, "site_config.0.cors.0.allowed_origins.#", "3"), + )}, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testCheckAzureRMAppServiceDestroy(s *terraform.State) error { client := testAccProvider.Meta().(*ArmClient).appServicesClient @@ -2476,3 +2503,41 @@ resource "azurerm_app_service" "test" { } `, rInt, location, rInt, rInt, tlsVersion) } + +func testAccAzureRMAppService_corsSettings(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_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}" + + site_config { + cors { + allowed_origins = [ + "http://www.contoso.com", + "www.contoso.com", + "contoso.com" + ] + support_credentials = true + } + } +} +`, rInt, location, rInt, rInt) +} diff --git a/website/docs/d/app_service.html.markdown b/website/docs/d/app_service.html.markdown index 1c771a698874..d310167f14fe 100644 --- a/website/docs/d/app_service.html.markdown +++ b/website/docs/d/app_service.html.markdown @@ -69,12 +69,30 @@ output "app_service_id" { --- +A `cors` block exports the following: + +* `allowed_origins` - A list of origins which are able to make cross-origin calls. + +* `support_credentials` - Are credentials supported? + +--- + +A `ip_restriction` block exports the following: + +* `ip_address` - The IP Address used for this IP Restriction. + +* `subnet_mask` - The Subnet mask used for this IP Restriction. + +--- + `site_config` supports the following: * `always_on` - Is the app be loaded at all times? * `app_command_line` - App command line to launch. +* `cors` - A `cors` block as defined above. + * `default_documents` - The ordering of default documents to load, if an address isn't specified. * `dotnet_framework_version` - The version of the .net framework's CLR used in this App Service. @@ -83,7 +101,7 @@ output "app_service_id" { * `ftps_state` - State of FTP / FTPS service for this AppService. -* `ip_restriction` - One or more `ip_restriction` blocks as defined below. +* `ip_restriction` - One or more `ip_restriction` blocks as defined above. * `java_version` - The version of Java in use. @@ -114,11 +132,3 @@ output "app_service_id" { * `websockets_enabled` - Are WebSockets enabled for this App Service? * `virtual_network_name` - The name of the Virtual Network which this App Service is attached to. - ---- - -`ip_restriction` exports the following: - -* `ip_address` - The IP Address used for this IP Restriction. - -* `subnet_mask` - The Subnet mask used for this IP Restriction. diff --git a/website/docs/r/app_service.html.markdown b/website/docs/r/app_service.html.markdown index d7cae64de733..30c3a7f80431 100644 --- a/website/docs/r/app_service.html.markdown +++ b/website/docs/r/app_service.html.markdown @@ -113,6 +113,8 @@ A `site_config` block supports the following: * `app_command_line` - (Optional) App command line to launch, e.g. `/sbin/myserver -b 0.0.0.0`. +* `cors` - (Optional) A `cors` block as defined below. + * `default_documents` - (Optional) The ordering of default documents to load, if an address isn't specified. * `dotnet_framework_version` - (Optional) The version of the .net framework's CLR used in this App Service. Possible values are `v2.0` (which will use the latest version of the .net framework for the .net CLR v2 - currently `.net 3.5`) and `v4.0` (which corresponds to the latest version of the .net CLR v4 - which at the time of writing is `.net 4.7.1`). [For more information on which .net CLR version to use based on the .net framework you're targeting - please see this table](https://en.wikipedia.org/wiki/.NET_Framework_version_history#Overview). Defaults to `v4.0`. @@ -161,6 +163,14 @@ Additional examples of how to run Containers via the `azurerm_app_service` resou --- +A `cors` block supports the following: + +* `allowed_origins` - (Optional) A list of origins which should be able to make cross-origin calls. `*` can be used to allow all calls. + +* `support_credentials` - (Optional) Are credentials supported? + +--- + A `ip_restriction` block supports the following: * `ip_address` - (Required) The IP Address used for this IP Restriction. diff --git a/website/docs/r/app_service_slot.html.markdown b/website/docs/r/app_service_slot.html.markdown index bb629cc8e74c..88ddd1e171d0 100644 --- a/website/docs/r/app_service_slot.html.markdown +++ b/website/docs/r/app_service_slot.html.markdown @@ -186,6 +186,8 @@ The following arguments are supported: * `always_on` - (Optional) Should the app be loaded at all times? Defaults to `false`. +* `cors` - (Optional) A `cors` block as defined below. + * `default_documents` - (Optional) The ordering of default documents to load, if an address isn't specified. * `dotnet_framework_version` - (Optional) The version of the .net framework's CLR used in this App Service Slot. Possible values are `v2.0` (which will use the latest version of the .net framework for the .net CLR v2 - currently `.net 3.5`) and `v4.0` (which corresponds to the latest version of the .net CLR v4 - which at the time of writing is `.net 4.7.1`). [For more information on which .net CLR version to use based on the .net framework you're targeting - please see this table](https://en.wikipedia.org/wiki/.NET_Framework_version_history#Overview). Defaults to `v4.0`. @@ -228,18 +230,29 @@ The following arguments are supported: --- -`ip_restriction` supports the following: +A `cors` block supports the following: -* `ip_address` - (Required) The IP Address used for this IP Restriction. +* `allowed_origins` - (Optional) A list of origins which should be able to make cross-origin calls. `*` can be used to allow all calls. -* `subnet_mask` - (Optional) The Subnet mask used for this IP Restriction. Defaults to `255.255.255.255`. +* `support_credentials` - (Optional) Are credentials supported? -`identity` supports the following: +--- + +A `identity` block supports the following: * `type` - (Required) Specifies the identity type of the App Service. At this time the only allowed value is `SystemAssigned`. ~> The assigned `principal_id` and `tenant_id` can be retrieved after the App Service Slot has been created. +--- + +A `ip_restriction` block supports the following: + +* `ip_address` - (Required) The IP Address used for this IP Restriction. + +* `subnet_mask` - (Optional) The Subnet mask used for this IP Restriction. Defaults to `255.255.255.255`. + + ## Attributes Reference The following attributes are exported: