diff --git a/internal/services/appservice/helpers/function_app_schema.go b/internal/services/appservice/helpers/function_app_schema.go index b468514cfad3..f6e501190350 100644 --- a/internal/services/appservice/helpers/function_app_schema.go +++ b/internal/services/appservice/helpers/function_app_schema.go @@ -36,7 +36,7 @@ type SiteConfigLinuxFunctionApp struct { LoadBalancing string `tfschema:"load_balancing_mode"` // TODO - Valid for FunctionApps? ManagedPipelineMode string `tfschema:"managed_pipeline_mode"` PreWarmedInstanceCount int `tfschema:"pre_warmed_instance_count"` - RemoteDebugging bool `tfschema:"remote_debugging"` + RemoteDebugging bool `tfschema:"remote_debugging_enabled"` RemoteDebuggingVersion string `tfschema:"remote_debugging_version"` RuntimeScaleMonitoring bool `tfschema:"runtime_scale_monitoring_enabled"` ScmIpRestriction []IpRestriction `tfschema:"scm_ip_restriction"` @@ -207,7 +207,7 @@ func SiteConfigSchemaLinuxFunctionApp() *pluginsdk.Schema { Description: "The number of pre-warmed instances for this function app. Only affects apps on an Elastic Premium plan.", }, - "remote_debugging": { + "remote_debugging_enabled": { Type: pluginsdk.TypeBool, Optional: true, Default: false, @@ -334,14 +334,323 @@ func SiteConfigSchemaLinuxFunctionApp() *pluginsdk.Schema { } } +type SiteConfigWindowsFunctionApp struct { + AlwaysOn bool `tfschema:"always_on"` + AppCommandLine string `tfschema:"app_command_line"` + ApiDefinition string `tfschema:"api_definition_url"` + ApiManagementConfigId string `tfschema:"api_management_api_id"` + AppInsightsInstrumentationKey string `tfschema:"application_insights_key"` // App Insights Instrumentation Key + AppInsightsConnectionString string `tfschema:"application_insights_connection_string"` + AppScaleLimit int `tfschema:"app_scale_limit"` + AppServiceLogs []FunctionAppAppServiceLogs `tfschema:"app_service_logs"` + DefaultDocuments []string `tfschema:"default_documents"` + ElasticInstanceMinimum int `tfschema:"elastic_instance_minimum"` + Http2Enabled bool `tfschema:"http2_enabled"` + IpRestriction []IpRestriction `tfschema:"ip_restriction"` + LoadBalancing string `tfschema:"load_balancing_mode"` // TODO - Valid for FunctionApps? + ManagedPipelineMode string `tfschema:"managed_pipeline_mode"` + PreWarmedInstanceCount int `tfschema:"pre_warmed_instance_count"` + RemoteDebugging bool `tfschema:"remote_debugging_enabled"` + RemoteDebuggingVersion string `tfschema:"remote_debugging_version"` + RuntimeScaleMonitoring bool `tfschema:"runtime_scale_monitoring_enabled"` + ScmIpRestriction []IpRestriction `tfschema:"scm_ip_restriction"` + ScmType string `tfschema:"scm_type"` // Computed? + ScmUseMainIpRestriction bool `tfschema:"scm_use_main_ip_restriction"` + Use32BitWorker bool `tfschema:"use_32_bit_worker"` + WebSockets bool `tfschema:"websockets_enabled"` + FtpsState string `tfschema:"ftps_state"` + HealthCheckPath string `tfschema:"health_check_path"` + HealthCheckEvictionTime int `tfschema:"health_check_eviction_time_in_min"` + NumberOfWorkers int `tfschema:"number_of_workers"` + ApplicationStack []ApplicationStackWindowsFunctionApp `tfschema:"application_stack"` + MinTlsVersion string `tfschema:"minimum_tls_version"` + ScmMinTlsVersion string `tfschema:"scm_minimum_tls_version"` + Cors []CorsSetting `tfschema:"cors"` + DetailedErrorLogging bool `tfschema:"detailed_error_logging"` + WindowsFxVersion string `tfschema:"windows_fx_version"` + VnetRouteAllEnabled bool `tfschema:"vnet_route_all_enabled"` // Not supported in Dynamic plans +} + +func SiteConfigSchemaWindowsFunctionApp() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Required: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "always_on": { + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, // Note - several factors change the default for this, so needs to be computed. + Description: "If this Windows Web App is Always On enabled. Defaults to `false`.", + }, + + "api_management_api_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: apimValidate.ApiID, + Description: "The ID of the API Management API for this Windows Function App.", + }, + + "api_definition_url": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.IsURLWithHTTPorHTTPS, + Description: "The URL of the API definition that describes this Windows Function App.", + }, + + "app_command_line": { + Type: pluginsdk.TypeString, + Optional: true, + Description: "The program and any arguments used to launch this app via the command line. (Example `node myapp.js`).", + }, + + "app_scale_limit": { + Type: pluginsdk.TypeInt, + Optional: true, + Computed: true, + Description: "The number of workers this function app can scale out to. Only applicable to apps on the Consumption and Premium plan.", + // TODO Validation? + }, + + "application_insights_key": { + Type: pluginsdk.TypeString, + Optional: true, + Sensitive: true, + RequiredWith: []string{ + "site_config.0.application_insights_connection_string", + }, + ValidateFunc: validation.StringIsNotEmpty, + Description: "The Instrumentation Key for connecting the Windows Function App to Application Insights.", + }, + + "application_insights_connection_string": { + Type: pluginsdk.TypeString, + Optional: true, + Sensitive: true, + RequiredWith: []string{ + "site_config.0.application_insights_key", + }, + ValidateFunc: validation.StringIsNotEmpty, + Description: "The Connection String for linking the Windows Function App to Application Insights.", + }, + + "application_stack": windowsFunctionAppStackSchema(), + + "app_service_logs": FunctionAppAppServiceLogsSchema(), + + "default_documents": { + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + Description: "Specifies a list of Default Documents for the Windows Web App.", + }, + + "elastic_instance_minimum": { + Type: pluginsdk.TypeInt, + Optional: true, + Computed: true, + Description: "The number of minimum instances for this Windows Function App. Only affects apps on Elastic Premium plans.", + }, + + "http2_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + Description: "Specifies if the http2 protocol should be enabled. Defaults to `false`.", + }, + + "ip_restriction": IpRestrictionSchema(), + + "scm_use_main_ip_restriction": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + Description: "Should the Windows Function App `ip_restriction` configuration be used for the SCM also.", + }, + + "scm_ip_restriction": IpRestrictionSchema(), + + "load_balancing_mode": { // Supported on Function Apps? + Type: pluginsdk.TypeString, + Optional: true, + Default: "LeastRequests", + ValidateFunc: validation.StringInSlice([]string{ + "LeastRequests", // Service default + "WeightedRoundRobin", + "LeastResponseTime", + "WeightedTotalTraffic", + "RequestHash", + "PerSiteRoundRobin", + }, false), + Description: "The Site load balancing mode. Possible values include: `WeightedRoundRobin`, `LeastRequests`, `LeastResponseTime`, `WeightedTotalTraffic`, `RequestHash`, `PerSiteRoundRobin`. Defaults to `LeastRequests` if omitted.", + }, + + "managed_pipeline_mode": { + Type: pluginsdk.TypeString, + Optional: true, + Default: string(web.ManagedPipelineModeIntegrated), + ValidateFunc: validation.StringInSlice([]string{ + string(web.ManagedPipelineModeClassic), + string(web.ManagedPipelineModeIntegrated), + }, false), + Description: "The Managed Pipeline mode. Possible values include: `Integrated`, `Classic`. Defaults to `Integrated`.", + }, + + "pre_warmed_instance_count": { + Type: pluginsdk.TypeInt, + Optional: true, + Computed: true, // Variable defaults depending on plan etc + Description: "The number of pre-warmed instances for this function app. Only affects apps on an Elastic Premium plan.", + }, + + "remote_debugging_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + Description: "Should Remote Debugging be enabled. Defaults to `false`.", + }, + + "remote_debugging_version": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + "VS2017", + "VS2019", + }, false), + Description: "The Remote Debugging Version. Possible values include `VS2017` and `VS2019`", + }, + + "runtime_scale_monitoring_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Description: "Should Functions Runtime Scale Monitoring be enabled.", + }, + + "scm_type": { + Type: pluginsdk.TypeString, + Computed: true, + Description: "The SCM Type in use by the Windows Function App.", + }, + + "use_32_bit_worker": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + Description: "Should the Windows Web App use a 32-bit worker.", + }, + + "websockets_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + Description: "Should Web Sockets be enabled. Defaults to `false`.", + }, + + "ftps_state": { + Type: pluginsdk.TypeString, + Optional: true, + Default: string(web.FtpsStateDisabled), + ValidateFunc: validation.StringInSlice([]string{ + string(web.FtpsStateAllAllowed), + string(web.FtpsStateDisabled), + string(web.FtpsStateFtpsOnly), + }, false), + Description: "State of FTP / FTPS service for this function app. Possible values include: `AllAllowed`, `FtpsOnly` and `Disabled`. Defaults to `Disabled`.", + }, + + "health_check_path": { + Type: pluginsdk.TypeString, + Optional: true, + Description: "The path to be checked for this function app health.", + }, + + "health_check_eviction_time_in_min": { // NOTE: Will evict the only node in single node configurations. + Type: pluginsdk.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: validation.IntBetween(2, 10), + Description: "The amount of time in minutes that a node is unhealthy before being removed from the load balancer. Possible values are between `2` and `10`. Defaults to `10`. Only valid in conjunction with `health_check_path`", + }, + + "number_of_workers": { + Type: pluginsdk.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: validation.IntBetween(1, 100), + Description: "The number of Workers for this Windows Function App.", + }, + + "minimum_tls_version": { + Type: pluginsdk.TypeString, + Optional: true, + Default: string(web.SupportedTLSVersionsOneFullStopTwo), + ValidateFunc: validation.StringInSlice([]string{ + string(web.SupportedTLSVersionsOneFullStopZero), + string(web.SupportedTLSVersionsOneFullStopOne), + string(web.SupportedTLSVersionsOneFullStopTwo), + }, false), + Description: "The configures the minimum version of TLS required for SSL requests. Possible values include: `1.0`, `1.1`, and `1.2`. Defaults to `1.2`.", + }, + + "scm_minimum_tls_version": { + Type: pluginsdk.TypeString, + Optional: true, + Default: string(web.SupportedTLSVersionsOneFullStopTwo), + ValidateFunc: validation.StringInSlice([]string{ + string(web.SupportedTLSVersionsOneFullStopZero), + string(web.SupportedTLSVersionsOneFullStopOne), + string(web.SupportedTLSVersionsOneFullStopTwo), + }, false), + Description: "Configures the minimum version of TLS required for SSL requests to the SCM site Possible values include: `1.0`, `1.1`, and `1.2`. Defaults to `1.2`.", + }, + + "cors": CorsSettingsSchema(), + + "vnet_route_all_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + Description: "Should all outbound traffic to have Virtual Network Security Groups and User Defined Routes applied? Defaults to `false`.", + }, + + "detailed_error_logging": { + Type: pluginsdk.TypeBool, + Computed: true, + Description: "Is detailed error logging enabled", + }, + + "windows_fx_version": { + Type: pluginsdk.TypeString, + Computed: true, + Description: "The Windows FX Version string.", + }, + }, + }, + } +} + type ApplicationStackLinuxFunctionApp struct { // Note - Function Apps differ to Web Apps here. They do not use the named properties in the SiteConfig block and exclusively use the app_settings map - DotNetVersion string `tfschema:"dotnet_version"` // Supported values `3.1`. Version 6 is in preview on Windows Only - NodeVersion string `tfschema:"node_version"` // Supported values `12LTS`, `14LTS` - PythonVersion string `tfschema:"python_version"` // Supported values `3.9`, `3.8`, `3.7` - JavaVersion string `tfschema:"java_version"` // Supported values `8`, `11` - CustomHandler bool `tfschema:"use_custom_runtime"` // Supported values `true` - Docker []ApplicationStackDocker `tfschema:"docker"` // Needs ElasticPremium or Basic (B1) Standard (S 1-3) or Premium(PxV2 or PxV3) LINUX Service Plan + DotNetVersion string `tfschema:"dotnet_version"` // Supported values `3.1`. Version 6 is in preview on Windows Only + NodeVersion string `tfschema:"node_version"` // Supported values `12LTS`, `14LTS` + PythonVersion string `tfschema:"python_version"` // Supported values `3.9`, `3.8`, `3.7` + PowerShellCoreVersion string `tfschema:"powershell_core_version"` // Supported values are `7.0` + JavaVersion string `tfschema:"java_version"` // Supported values `8`, `11` + CustomHandler bool `tfschema:"use_custom_runtime"` // Supported values `true` + Docker []ApplicationStackDocker `tfschema:"docker"` // Needs ElasticPremium or Basic (B1) Standard (S 1-3) or Premium(PxV2 or PxV3) LINUX Service Plan +} + +type ApplicationStackWindowsFunctionApp struct { + DotNetVersion string `tfschema:"dotnet_version"` // Supported values `3.1`. Version 6 is in preview on Windows Only + NodeVersion string `tfschema:"node_version"` // Supported values `12LTS`, `14LTS` + JavaVersion string `tfschema:"java_version"` // Supported values `8`, `11` + PowerShellCoreVersion string `tfschema:"powershell_core_version"` // Supported values are `7.0` + CustomHandler bool `tfschema:"use_custom_runtime"` // Supported values `true` } type ApplicationStackDocker struct { @@ -371,6 +680,7 @@ func linuxFunctionAppStackSchema() *pluginsdk.Schema { "site_config.0.application_stack.0.python_version", "site_config.0.application_stack.0.java_version", "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.powershell_core_version", "site_config.0.application_stack.0.docker", "site_config.0.application_stack.0.use_custom_runtime", }, @@ -390,6 +700,7 @@ func linuxFunctionAppStackSchema() *pluginsdk.Schema { "site_config.0.application_stack.0.python_version", "site_config.0.application_stack.0.java_version", "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.powershell_core_version", "site_config.0.application_stack.0.docker", "site_config.0.application_stack.0.use_custom_runtime", }, @@ -408,32 +719,52 @@ func linuxFunctionAppStackSchema() *pluginsdk.Schema { "site_config.0.application_stack.0.python_version", "site_config.0.application_stack.0.java_version", "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.powershell_core_version", "site_config.0.application_stack.0.docker", "site_config.0.application_stack.0.use_custom_runtime", }, Description: "The version of Node to use. Possible values include `12`, and `14`", }, - "java_version": { + "powershell_core_version": { Type: pluginsdk.TypeString, Optional: true, ValidateFunc: validation.StringInSlice([]string{ - "8", - "11", + "7", }, false), ExactlyOneOf: []string{ "site_config.0.application_stack.0.dotnet_version", "site_config.0.application_stack.0.python_version", "site_config.0.application_stack.0.java_version", "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.powershell_core_version", "site_config.0.application_stack.0.docker", "site_config.0.application_stack.0.use_custom_runtime", }, - Description: "The version of Java to use. Possible values are `8`, and `11`", + Description: "The version of PowerShell Core to use. Possibles values are `7`.", }, - "docker": { - Type: pluginsdk.TypeList, + "java_version": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "8", + "11", + }, false), + ExactlyOneOf: []string{ + "site_config.0.application_stack.0.dotnet_version", + "site_config.0.application_stack.0.python_version", + "site_config.0.application_stack.0.java_version", + "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.powershell_core_version", + "site_config.0.application_stack.0.docker", + "site_config.0.application_stack.0.use_custom_runtime", + }, + Description: "The version of Java to use. Possible values are `8`, and `11`", + }, + + "docker": { + Type: pluginsdk.TypeList, Optional: true, Elem: &pluginsdk.Resource{ Schema: map[string]*schema.Schema{ @@ -444,93 +775,446 @@ func linuxFunctionAppStackSchema() *pluginsdk.Schema { Description: "The URL of the docker registry.", }, - "registry_username": { - Type: pluginsdk.TypeString, - Optional: true, - Sensitive: true, - ValidateFunc: validation.StringIsNotEmpty, - Description: "The username to use for connections to the registry.", - }, + "registry_username": { + Type: pluginsdk.TypeString, + Optional: true, + Sensitive: true, + ValidateFunc: validation.StringIsNotEmpty, + Description: "The username to use for connections to the registry.", + }, + + "registry_password": { + Type: pluginsdk.TypeString, + Optional: true, + Sensitive: true, // Note: whilst it's not a good idea, this _can_ be blank... + Description: "The password for the account to use to connect to the registry.", + }, + + "image_name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + Description: "The name of the Docker image to use.", + }, + + "image_tag": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + Description: "The image tag of the image to use.", + }, + }, + }, + ExactlyOneOf: []string{ + "site_config.0.application_stack.0.dotnet_version", + "site_config.0.application_stack.0.python_version", + "site_config.0.application_stack.0.java_version", + "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.powershell_core_version", + "site_config.0.application_stack.0.docker", + "site_config.0.application_stack.0.use_custom_runtime", + }, + Description: "A docker block", + }, + + "use_custom_runtime": { + Type: pluginsdk.TypeBool, + Optional: true, + ExactlyOneOf: []string{ + "site_config.0.application_stack.0.dotnet_version", + "site_config.0.application_stack.0.python_version", + "site_config.0.application_stack.0.java_version", + "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.powershell_core_version", + "site_config.0.application_stack.0.docker", + "site_config.0.application_stack.0.use_custom_runtime", + }, + }, + }, + }, + } +} + +func windowsFunctionAppStackSchema() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "dotnet_version": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "3.1", + "6", + }, false), + ExactlyOneOf: []string{ + "site_config.0.application_stack.0.dotnet_version", + "site_config.0.application_stack.0.java_version", + "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.powershell_core_version", + "site_config.0.application_stack.0.use_custom_runtime", + }, + Description: "The version of .Net. Possible values are `3.1` and `6`", + }, + + "node_version": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "12", + "14", + }, false), + ExactlyOneOf: []string{ + "site_config.0.application_stack.0.dotnet_version", + "site_config.0.application_stack.0.java_version", + "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.powershell_core_version", + "site_config.0.application_stack.0.use_custom_runtime", + }, + Description: "The version of Node to use. Possible values include `12`, and `14`", + }, + + "java_version": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "8", + "11", + }, false), + ExactlyOneOf: []string{ + "site_config.0.application_stack.0.dotnet_version", + "site_config.0.application_stack.0.java_version", + "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.powershell_core_version", + "site_config.0.application_stack.0.use_custom_runtime", + }, + Description: "The version of Java to use. Possible values are `8`, and `11`", + }, + + "powershell_core_version": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "7", + }, false), + ExactlyOneOf: []string{ + "site_config.0.application_stack.0.dotnet_version", + "site_config.0.application_stack.0.java_version", + "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.powershell_core_version", + "site_config.0.application_stack.0.use_custom_runtime", + }, + Description: "The PowerShell Core version to use. Possible values are `7`.", + }, + + "use_custom_runtime": { + Type: pluginsdk.TypeBool, + Optional: true, + ExactlyOneOf: []string{ + "site_config.0.application_stack.0.dotnet_version", + "site_config.0.application_stack.0.java_version", + "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.powershell_core_version", + "site_config.0.application_stack.0.use_custom_runtime", + }, + Description: "Does the Function App use a custom Application Stack?", + }, + }, + }, + } +} + +type FunctionAppAppServiceLogs struct { + DiskQuotaMB int `tfschema:"disk_quota_mb"` + RetentionPeriodDays int `tfschema:"retention_period_days"` +} + +func FunctionAppAppServiceLogsSchema() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*schema.Schema{ + "disk_quota_mb": { + Type: pluginsdk.TypeInt, + Optional: true, + Default: 35, + ValidateFunc: validation.IntBetween(25, 100), + }, + "retention_period_days": { + Type: pluginsdk.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(0, 99999), + }, + }, + }, + } +} + +func ExpandSiteConfigLinuxFunctionApp(siteConfig []SiteConfigLinuxFunctionApp, existing *web.SiteConfig, metadata sdk.ResourceMetaData, version string, storageString string, storageUsesMSI bool) (*web.SiteConfig, error) { + if len(siteConfig) == 0 { + return nil, nil + } + expanded := &web.SiteConfig{} + if existing != nil { + expanded = existing + // need to zero fxversion to re-calculate based on changes below or removing app_stack doesn't apply + expanded.LinuxFxVersion = utils.String("") + } + + appSettings := make([]web.NameValuePair, 0) + + appSettings = append(appSettings, web.NameValuePair{ + Name: utils.String("FUNCTIONS_EXTENSION_VERSION"), + Value: utils.String(version), + }) + + if storageUsesMSI { + appSettings = append(appSettings, web.NameValuePair{ + Name: utils.String("AzureWebJobsStorage__accountName"), + Value: utils.String(storageString), + }) + } else { + appSettings = append(appSettings, web.NameValuePair{ + Name: utils.String("AzureWebJobsStorage"), + Value: utils.String(storageString), + }) + } + + linuxSiteConfig := siteConfig[0] + + if metadata.ResourceData.HasChange("site_config.0.health_check_path") { + if linuxSiteConfig.HealthCheckPath != "" && metadata.ResourceData.HasChange("site_config.0.health_check_eviction_time_in_min") { + v := strconv.Itoa(linuxSiteConfig.HealthCheckEvictionTime) + appSettings = append(appSettings, web.NameValuePair{ + Name: utils.String("WEBSITE_HEALTHCHECK_MAXPINGFAILURES"), + Value: utils.String(v), + }) + } + } + + expanded.AlwaysOn = utils.Bool(linuxSiteConfig.AlwaysOn) + + if metadata.ResourceData.HasChange("site_config.0.app_scale_limit") { + expanded.FunctionAppScaleLimit = utils.Int32(int32(linuxSiteConfig.AppScaleLimit)) + } + + if linuxSiteConfig.AppInsightsConnectionString != "" { + appSettings = append(appSettings, web.NameValuePair{ + Name: utils.String("APPLICATIONINSIGHTS_CONNECTION_STRING"), + Value: utils.String(linuxSiteConfig.AppInsightsConnectionString), + }) + } + + if linuxSiteConfig.AppInsightsInstrumentationKey != "" { + appSettings = append(appSettings, web.NameValuePair{ + Name: utils.String("APPINSIGHTS_INSTRUMENTATIONKEY"), + Value: utils.String(linuxSiteConfig.AppInsightsInstrumentationKey), + }) + } + + if metadata.ResourceData.HasChange("site_config.0.api_management_api_id") { + expanded.APIManagementConfig = &web.APIManagementConfig{ + ID: utils.String(linuxSiteConfig.ApiManagementConfigId), + } + } + + if metadata.ResourceData.HasChange("site_config.0.api_definition_url") { + expanded.APIDefinition = &web.APIDefinitionInfo{ + URL: utils.String(linuxSiteConfig.ApiDefinition), + } + } + + if metadata.ResourceData.HasChange("site_config.0.app_command_line") { + expanded.AppCommandLine = utils.String(linuxSiteConfig.AppCommandLine) + } + + if metadata.ResourceData.HasChange("site_config.0.application_stack") && len(linuxSiteConfig.ApplicationStack) > 0 { + if len(linuxSiteConfig.ApplicationStack) > 0 { + linuxAppStack := linuxSiteConfig.ApplicationStack[0] + if linuxAppStack.DotNetVersion != "" { + appSettings = append(appSettings, web.NameValuePair{ + Name: utils.String("FUNCTIONS_WORKER_RUNTIME"), + Value: utils.String("dotnet"), + }) + linuxSiteConfig.LinuxFxVersion = fmt.Sprintf("DOTNET|%s", linuxAppStack.DotNetVersion) + } + + if linuxAppStack.NodeVersion != "" { + appSettings = append(appSettings, web.NameValuePair{ + Name: utils.String("FUNCTIONS_WORKER_RUNTIME"), + Value: utils.String("node"), + }) + appSettings = append(appSettings, web.NameValuePair{ + Name: utils.String("WEBSITE_NODE_DEFAULT_VERSION"), + Value: utils.String(linuxAppStack.NodeVersion), + }) + linuxSiteConfig.LinuxFxVersion = fmt.Sprintf("Node|%s", linuxAppStack.NodeVersion) + } + + if linuxAppStack.PythonVersion != "" { + appSettings = append(appSettings, web.NameValuePair{ + Name: utils.String("FUNCTIONS_WORKER_RUNTIME"), + Value: utils.String("python"), + }) + linuxSiteConfig.LinuxFxVersion = fmt.Sprintf("Python|%s", linuxAppStack.PythonVersion) + } + + if linuxAppStack.JavaVersion != "" { + appSettings = append(appSettings, web.NameValuePair{ + Name: utils.String("FUNCTIONS_WORKER_RUNTIME"), + Value: utils.String("java"), + }) + linuxSiteConfig.LinuxFxVersion = fmt.Sprintf("Java|%s", linuxAppStack.JavaVersion) + } + + if linuxAppStack.PowerShellCoreVersion != "" { + appSettings = append(appSettings, web.NameValuePair{ + Name: utils.String("FUNCTIONS_WORKER_RUNTIME"), + Value: utils.String("powershell"), + }) + linuxSiteConfig.LinuxFxVersion = fmt.Sprintf("PowerShell|%s", linuxAppStack.PowerShellCoreVersion) + } + + if linuxAppStack.CustomHandler { + appSettings = append(appSettings, web.NameValuePair{ + Name: utils.String("FUNCTIONS_WORKER_RUNTIME"), + Value: utils.String("custom"), + }) + linuxSiteConfig.LinuxFxVersion = "" // Custom needs an explicit empty string here + } + + if linuxAppStack.Docker != nil && len(linuxAppStack.Docker) == 1 { + dockerConfig := linuxAppStack.Docker[0] + appSettings = append(appSettings, web.NameValuePair{ + Name: utils.String("DOCKER_REGISTRY_SERVER_URL"), + Value: utils.String(dockerConfig.RegistryURL), + }) + appSettings = append(appSettings, web.NameValuePair{ + Name: utils.String("DOCKER_REGISTRY_SERVER_USERNAME"), + Value: utils.String(dockerConfig.RegistryUsername), + }) + appSettings = append(appSettings, web.NameValuePair{ + Name: utils.String("DOCKER_REGISTRY_SERVER_PASSWORD"), + Value: utils.String(dockerConfig.RegistryPassword), + }) + linuxSiteConfig.LinuxFxVersion = fmt.Sprintf("DOCKER|%s/%s:%s", dockerConfig.RegistryURL, dockerConfig.ImageName, dockerConfig.ImageTag) + } + } else { + appSettings = append(appSettings, web.NameValuePair{ + Name: utils.String("FUNCTIONS_WORKER_RUNTIME"), + Value: utils.String(""), + }) + linuxSiteConfig.LinuxFxVersion = "" + } + } + + if metadata.ResourceData.HasChange("site_config.0.container_registry_use_managed_identity") { + expanded.AcrUseManagedIdentityCreds = utils.Bool(linuxSiteConfig.UseManagedIdentityACR) + } + + if metadata.ResourceData.HasChange("site_config.0.vnet_route_all_enabled") { + expanded.VnetRouteAllEnabled = utils.Bool(linuxSiteConfig.VnetRouteAllEnabled) + } + + if metadata.ResourceData.HasChange("site_config.0.container_registry_managed_identity_client_id") { + expanded.AcrUserManagedIdentityID = utils.String(linuxSiteConfig.ContainerRegistryMSI) + } + + if metadata.ResourceData.HasChange("site_config.0.default_documents") { + expanded.DefaultDocuments = &linuxSiteConfig.DefaultDocuments + } + + if metadata.ResourceData.HasChange("site_config.0.http2_enabled") { + expanded.HTTP20Enabled = utils.Bool(linuxSiteConfig.Http2Enabled) + } + + if metadata.ResourceData.HasChange("site_config.0.ip_restriction") { + ipRestrictions, err := ExpandIpRestrictions(linuxSiteConfig.IpRestriction) + if err != nil { + return nil, err + } + expanded.IPSecurityRestrictions = ipRestrictions + } + + if metadata.ResourceData.HasChange("site_config.0.scm_use_main_ip_restriction") { + expanded.ScmIPSecurityRestrictionsUseMain = utils.Bool(linuxSiteConfig.ScmUseMainIpRestriction) + } + + if metadata.ResourceData.HasChange("site_config.0.scm_ip_restriction") { + scmIpRestrictions, err := ExpandIpRestrictions(linuxSiteConfig.ScmIpRestriction) + if err != nil { + return nil, err + } + expanded.ScmIPSecurityRestrictions = scmIpRestrictions + } + + if metadata.ResourceData.HasChange("site_config.0.load_balancing_mode") { + expanded.LoadBalancing = web.SiteLoadBalancing(linuxSiteConfig.LoadBalancing) + } + + if metadata.ResourceData.HasChange("site_config.0.managed_pipeline_mode") { + expanded.ManagedPipelineMode = web.ManagedPipelineMode(linuxSiteConfig.ManagedPipelineMode) + } + + if metadata.ResourceData.HasChange("site_config.0.remote_debugging_enabled") { + expanded.RemoteDebuggingEnabled = utils.Bool(linuxSiteConfig.RemoteDebugging) + } + + if metadata.ResourceData.HasChange("site_config.0.remote_debugging_version") { + expanded.RemoteDebuggingVersion = utils.String(linuxSiteConfig.RemoteDebuggingVersion) + } + + if metadata.ResourceData.HasChange("site_config.0.use_32_bit_worker") { + expanded.Use32BitWorkerProcess = utils.Bool(linuxSiteConfig.Use32BitWorker) + } - "registry_password": { - Type: pluginsdk.TypeString, - Optional: true, - Sensitive: true, // Note: whilst it's not a good idea, this _can_ be blank... - Description: "The password for the account to use to connect to the registry.", - }, + if metadata.ResourceData.HasChange("site_config.0.websockets_enabled") { + expanded.WebSocketsEnabled = utils.Bool(linuxSiteConfig.WebSockets) + } - "image_name": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - Description: "The name of the Docker image to use.", - }, + if metadata.ResourceData.HasChange("site_config.0.ftps_state") { + expanded.FtpsState = web.FtpsState(linuxSiteConfig.FtpsState) + } - "image_tag": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - Description: "The image tag of the image to use.", - }, - }, - }, - ExactlyOneOf: []string{ - "site_config.0.application_stack.0.dotnet_version", - "site_config.0.application_stack.0.python_version", - "site_config.0.application_stack.0.java_version", - "site_config.0.application_stack.0.node_version", - "site_config.0.application_stack.0.docker", - "site_config.0.application_stack.0.use_custom_runtime", - }, - Description: "A docker block", - }, + if metadata.ResourceData.HasChange("site_config.0.health_check_path") { + expanded.HealthCheckPath = utils.String(linuxSiteConfig.HealthCheckPath) + } - "use_custom_runtime": { - Type: pluginsdk.TypeBool, - Optional: true, - ExactlyOneOf: []string{ - "site_config.0.application_stack.0.dotnet_version", - "site_config.0.application_stack.0.python_version", - "site_config.0.application_stack.0.java_version", - "site_config.0.application_stack.0.node_version", - "site_config.0.application_stack.0.docker", - "site_config.0.application_stack.0.use_custom_runtime", - }, - }, - }, - }, + if metadata.ResourceData.HasChange("site_config.0.number_of_workers") { + expanded.NumberOfWorkers = utils.Int32(int32(linuxSiteConfig.NumberOfWorkers)) } -} -type FunctionAppAppServiceLogs struct { - DiskQuotaMB int `tfschema:"disk_quota_mb"` - RetentionPeriodDays int `tfschema:"retention_period_days"` -} + if metadata.ResourceData.HasChange("site_config.0.minimum_tls_version") { + expanded.MinTLSVersion = web.SupportedTLSVersions(linuxSiteConfig.MinTlsVersion) + } -func FunctionAppAppServiceLogsSchema() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Optional: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*schema.Schema{ - "disk_quota_mb": { - Type: pluginsdk.TypeInt, - Optional: true, - Default: 35, - ValidateFunc: validation.IntBetween(25, 100), - }, - "retention_period_days": { - Type: pluginsdk.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(0, 99999), - }, - }, - }, + if metadata.ResourceData.HasChange("site_config.0.scm_minimum_tls_version") { + expanded.ScmMinTLSVersion = web.SupportedTLSVersions(linuxSiteConfig.ScmMinTlsVersion) + } + + if metadata.ResourceData.HasChange("site_config.0.cors") { + cors := ExpandCorsSettings(linuxSiteConfig.Cors) + expanded.Cors = cors + } + + if metadata.ResourceData.HasChange("site_config.0.pre_warmed_instance_count") { + expanded.PreWarmedInstanceCount = utils.Int32(int32(linuxSiteConfig.PreWarmedInstanceCount)) } + + if metadata.ResourceData.HasChange("site_config.0.vnet_route_all_enabled") { + expanded.VnetRouteAllEnabled = utils.Bool(linuxSiteConfig.VnetRouteAllEnabled) + } + + expanded.AppSettings = &appSettings + + return expanded, nil } -func ExpandSiteConfigLinuxFunctionApp(siteConfig []SiteConfigLinuxFunctionApp, existing *web.SiteConfig, metadata sdk.ResourceMetaData, version string, storageString string, storageUsesMSI bool) (*web.SiteConfig, error) { +func ExpandSiteConfigWindowsFunctionApp(siteConfig []SiteConfigWindowsFunctionApp, existing *web.SiteConfig, metadata sdk.ResourceMetaData, version string, storageString string, storageUsesMSI bool) (*web.SiteConfig, error) { if len(siteConfig) == 0 { return nil, nil } @@ -560,11 +1244,11 @@ func ExpandSiteConfigLinuxFunctionApp(siteConfig []SiteConfigLinuxFunctionApp, e }) } - linuxSiteConfig := siteConfig[0] + windowsSiteConfig := siteConfig[0] if metadata.ResourceData.HasChange("site_config.0.health_check_path") { - if linuxSiteConfig.HealthCheckPath != "" && metadata.ResourceData.HasChange("site_config.0.health_check_eviction_time_in_min") { - v := strconv.Itoa(linuxSiteConfig.HealthCheckEvictionTime) + if windowsSiteConfig.HealthCheckPath != "" && metadata.ResourceData.HasChange("site_config.0.health_check_eviction_time_in_min") { + v := strconv.Itoa(windowsSiteConfig.HealthCheckEvictionTime) appSettings = append(appSettings, web.NameValuePair{ Name: utils.String("WEBSITE_HEALTHCHECK_MAXPINGFAILURES"), Value: utils.String(v), @@ -572,51 +1256,51 @@ func ExpandSiteConfigLinuxFunctionApp(siteConfig []SiteConfigLinuxFunctionApp, e } } - expanded.AlwaysOn = utils.Bool(linuxSiteConfig.AlwaysOn) + expanded.AlwaysOn = utils.Bool(windowsSiteConfig.AlwaysOn) if metadata.ResourceData.HasChange("site_config.0.app_scale_limit") { - expanded.FunctionAppScaleLimit = utils.Int32(int32(linuxSiteConfig.AppScaleLimit)) + expanded.FunctionAppScaleLimit = utils.Int32(int32(windowsSiteConfig.AppScaleLimit)) } - if linuxSiteConfig.AppInsightsConnectionString != "" { + if windowsSiteConfig.AppInsightsConnectionString != "" { appSettings = append(appSettings, web.NameValuePair{ Name: utils.String("APPLICATIONINSIGHTS_CONNECTION_STRING"), - Value: utils.String(linuxSiteConfig.AppInsightsConnectionString), + Value: utils.String(windowsSiteConfig.AppInsightsConnectionString), }) } - if linuxSiteConfig.AppInsightsInstrumentationKey != "" { + if windowsSiteConfig.AppInsightsInstrumentationKey != "" { appSettings = append(appSettings, web.NameValuePair{ Name: utils.String("APPINSIGHTS_INSTRUMENTATIONKEY"), - Value: utils.String(linuxSiteConfig.AppInsightsInstrumentationKey), + Value: utils.String(windowsSiteConfig.AppInsightsInstrumentationKey), }) } if metadata.ResourceData.HasChange("site_config.0.api_management_api_id") { expanded.APIManagementConfig = &web.APIManagementConfig{ - ID: utils.String(linuxSiteConfig.ApiManagementConfigId), + ID: utils.String(windowsSiteConfig.ApiManagementConfigId), } } if metadata.ResourceData.HasChange("site_config.0.api_definition_url") { expanded.APIDefinition = &web.APIDefinitionInfo{ - URL: utils.String(linuxSiteConfig.ApiDefinition), + URL: utils.String(windowsSiteConfig.ApiDefinition), } } if metadata.ResourceData.HasChange("site_config.0.app_command_line") { - expanded.AppCommandLine = utils.String(linuxSiteConfig.AppCommandLine) + expanded.AppCommandLine = utils.String(windowsSiteConfig.AppCommandLine) } - if metadata.ResourceData.HasChange("site_config.0.application_stack") && len(linuxSiteConfig.ApplicationStack) > 0 { - if len(linuxSiteConfig.ApplicationStack) > 0 { - linuxAppStack := linuxSiteConfig.ApplicationStack[0] + if metadata.ResourceData.HasChange("site_config.0.application_stack") && len(windowsSiteConfig.ApplicationStack) > 0 { + if len(windowsSiteConfig.ApplicationStack) > 0 { + linuxAppStack := windowsSiteConfig.ApplicationStack[0] if linuxAppStack.DotNetVersion != "" { appSettings = append(appSettings, web.NameValuePair{ Name: utils.String("FUNCTIONS_WORKER_RUNTIME"), Value: utils.String("dotnet"), }) - linuxSiteConfig.LinuxFxVersion = fmt.Sprintf("DOTNET|%s", linuxAppStack.DotNetVersion) + windowsSiteConfig.WindowsFxVersion = fmt.Sprintf("DOTNET|%s", linuxAppStack.DotNetVersion) } if linuxAppStack.NodeVersion != "" { @@ -628,23 +1312,23 @@ func ExpandSiteConfigLinuxFunctionApp(siteConfig []SiteConfigLinuxFunctionApp, e Name: utils.String("WEBSITE_NODE_DEFAULT_VERSION"), Value: utils.String(linuxAppStack.NodeVersion), }) - linuxSiteConfig.LinuxFxVersion = fmt.Sprintf("Node|%s", linuxAppStack.NodeVersion) + windowsSiteConfig.WindowsFxVersion = fmt.Sprintf("Node|%s", linuxAppStack.NodeVersion) } - if linuxAppStack.PythonVersion != "" { + if linuxAppStack.JavaVersion != "" { appSettings = append(appSettings, web.NameValuePair{ Name: utils.String("FUNCTIONS_WORKER_RUNTIME"), - Value: utils.String("python"), + Value: utils.String("java"), }) - linuxSiteConfig.LinuxFxVersion = fmt.Sprintf("Python|%s", linuxAppStack.PythonVersion) + windowsSiteConfig.WindowsFxVersion = fmt.Sprintf("Java|%s", linuxAppStack.JavaVersion) } - if linuxAppStack.JavaVersion != "" { + if linuxAppStack.PowerShellCoreVersion != "" { appSettings = append(appSettings, web.NameValuePair{ Name: utils.String("FUNCTIONS_WORKER_RUNTIME"), - Value: utils.String("java"), + Value: utils.String("powershell"), }) - linuxSiteConfig.LinuxFxVersion = fmt.Sprintf("Java|%s", linuxAppStack.JavaVersion) + windowsSiteConfig.WindowsFxVersion = fmt.Sprintf("PowerShell|%s", linuxAppStack.PowerShellCoreVersion) } if linuxAppStack.CustomHandler { @@ -652,56 +1336,31 @@ func ExpandSiteConfigLinuxFunctionApp(siteConfig []SiteConfigLinuxFunctionApp, e Name: utils.String("FUNCTIONS_WORKER_RUNTIME"), Value: utils.String("custom"), }) - linuxSiteConfig.LinuxFxVersion = "" // Custom needs an explicit empty string here - } - - if linuxAppStack.Docker != nil && len(linuxAppStack.Docker) == 1 { - dockerConfig := linuxAppStack.Docker[0] - appSettings = append(appSettings, web.NameValuePair{ - Name: utils.String("DOCKER_REGISTRY_SERVER_URL"), - Value: utils.String(dockerConfig.RegistryURL), - }) - appSettings = append(appSettings, web.NameValuePair{ - Name: utils.String("DOCKER_REGISTRY_SERVER_USERNAME"), - Value: utils.String(dockerConfig.RegistryUsername), - }) - appSettings = append(appSettings, web.NameValuePair{ - Name: utils.String("DOCKER_REGISTRY_SERVER_PASSWORD"), - Value: utils.String(dockerConfig.RegistryPassword), - }) - linuxSiteConfig.LinuxFxVersion = fmt.Sprintf("DOCKER|%s/%s:%s", dockerConfig.RegistryURL, dockerConfig.ImageName, dockerConfig.ImageTag) + windowsSiteConfig.WindowsFxVersion = "" // Custom needs an explicit empty string here } } else { appSettings = append(appSettings, web.NameValuePair{ Name: utils.String("FUNCTIONS_WORKER_RUNTIME"), Value: utils.String(""), }) - linuxSiteConfig.LinuxFxVersion = "" + windowsSiteConfig.WindowsFxVersion = "" } } - if metadata.ResourceData.HasChange("site_config.0.container_registry_use_managed_identity") { - expanded.AcrUseManagedIdentityCreds = utils.Bool(linuxSiteConfig.UseManagedIdentityACR) - } - if metadata.ResourceData.HasChange("site_config.0.vnet_route_all_enabled") { - expanded.VnetRouteAllEnabled = utils.Bool(linuxSiteConfig.VnetRouteAllEnabled) - } - - if metadata.ResourceData.HasChange("site_config.0.container_registry_managed_identity_client_id") { - expanded.AcrUserManagedIdentityID = utils.String(linuxSiteConfig.ContainerRegistryMSI) + expanded.VnetRouteAllEnabled = utils.Bool(windowsSiteConfig.VnetRouteAllEnabled) } if metadata.ResourceData.HasChange("site_config.0.default_documents") { - expanded.DefaultDocuments = &linuxSiteConfig.DefaultDocuments + expanded.DefaultDocuments = &windowsSiteConfig.DefaultDocuments } if metadata.ResourceData.HasChange("site_config.0.http2_enabled") { - expanded.HTTP20Enabled = utils.Bool(linuxSiteConfig.Http2Enabled) + expanded.HTTP20Enabled = utils.Bool(windowsSiteConfig.Http2Enabled) } if metadata.ResourceData.HasChange("site_config.0.ip_restriction") { - ipRestrictions, err := ExpandIpRestrictions(linuxSiteConfig.IpRestriction) + ipRestrictions, err := ExpandIpRestrictions(windowsSiteConfig.IpRestriction) if err != nil { return nil, err } @@ -709,11 +1368,11 @@ func ExpandSiteConfigLinuxFunctionApp(siteConfig []SiteConfigLinuxFunctionApp, e } if metadata.ResourceData.HasChange("site_config.0.scm_use_main_ip_restriction") { - expanded.ScmIPSecurityRestrictionsUseMain = utils.Bool(linuxSiteConfig.ScmUseMainIpRestriction) + expanded.ScmIPSecurityRestrictionsUseMain = utils.Bool(windowsSiteConfig.ScmUseMainIpRestriction) } if metadata.ResourceData.HasChange("site_config.0.scm_ip_restriction") { - scmIpRestrictions, err := ExpandIpRestrictions(linuxSiteConfig.ScmIpRestriction) + scmIpRestrictions, err := ExpandIpRestrictions(windowsSiteConfig.ScmIpRestriction) if err != nil { return nil, err } @@ -721,60 +1380,60 @@ func ExpandSiteConfigLinuxFunctionApp(siteConfig []SiteConfigLinuxFunctionApp, e } if metadata.ResourceData.HasChange("site_config.0.load_balancing_mode") { - expanded.LoadBalancing = web.SiteLoadBalancing(linuxSiteConfig.LoadBalancing) + expanded.LoadBalancing = web.SiteLoadBalancing(windowsSiteConfig.LoadBalancing) } if metadata.ResourceData.HasChange("site_config.0.managed_pipeline_mode") { - expanded.ManagedPipelineMode = web.ManagedPipelineMode(linuxSiteConfig.ManagedPipelineMode) + expanded.ManagedPipelineMode = web.ManagedPipelineMode(windowsSiteConfig.ManagedPipelineMode) } - if metadata.ResourceData.HasChange("site_config.0.remote_debugging") { - expanded.RemoteDebuggingEnabled = utils.Bool(linuxSiteConfig.RemoteDebugging) + if metadata.ResourceData.HasChange("site_config.0.remote_debugging_enabled") { + expanded.RemoteDebuggingEnabled = utils.Bool(windowsSiteConfig.RemoteDebugging) } if metadata.ResourceData.HasChange("site_config.0.remote_debugging_version") { - expanded.RemoteDebuggingVersion = utils.String(linuxSiteConfig.RemoteDebuggingVersion) + expanded.RemoteDebuggingVersion = utils.String(windowsSiteConfig.RemoteDebuggingVersion) } if metadata.ResourceData.HasChange("site_config.0.use_32_bit_worker") { - expanded.Use32BitWorkerProcess = utils.Bool(linuxSiteConfig.Use32BitWorker) + expanded.Use32BitWorkerProcess = utils.Bool(windowsSiteConfig.Use32BitWorker) } if metadata.ResourceData.HasChange("site_config.0.websockets_enabled") { - expanded.WebSocketsEnabled = utils.Bool(linuxSiteConfig.WebSockets) + expanded.WebSocketsEnabled = utils.Bool(windowsSiteConfig.WebSockets) } if metadata.ResourceData.HasChange("site_config.0.ftps_state") { - expanded.FtpsState = web.FtpsState(linuxSiteConfig.FtpsState) + expanded.FtpsState = web.FtpsState(windowsSiteConfig.FtpsState) } if metadata.ResourceData.HasChange("site_config.0.health_check_path") { - expanded.HealthCheckPath = utils.String(linuxSiteConfig.HealthCheckPath) + expanded.HealthCheckPath = utils.String(windowsSiteConfig.HealthCheckPath) } if metadata.ResourceData.HasChange("site_config.0.number_of_workers") { - expanded.NumberOfWorkers = utils.Int32(int32(linuxSiteConfig.NumberOfWorkers)) + expanded.NumberOfWorkers = utils.Int32(int32(windowsSiteConfig.NumberOfWorkers)) } if metadata.ResourceData.HasChange("site_config.0.minimum_tls_version") { - expanded.MinTLSVersion = web.SupportedTLSVersions(linuxSiteConfig.MinTlsVersion) + expanded.MinTLSVersion = web.SupportedTLSVersions(windowsSiteConfig.MinTlsVersion) } if metadata.ResourceData.HasChange("site_config.0.scm_minimum_tls_version") { - expanded.ScmMinTLSVersion = web.SupportedTLSVersions(linuxSiteConfig.ScmMinTlsVersion) + expanded.ScmMinTLSVersion = web.SupportedTLSVersions(windowsSiteConfig.ScmMinTlsVersion) } if metadata.ResourceData.HasChange("site_config.0.cors") { - cors := ExpandCorsSettings(linuxSiteConfig.Cors) + cors := ExpandCorsSettings(windowsSiteConfig.Cors) expanded.Cors = cors } if metadata.ResourceData.HasChange("site_config.0.pre_warmed_instance_count") { - expanded.PreWarmedInstanceCount = utils.Int32(int32(linuxSiteConfig.PreWarmedInstanceCount)) + expanded.PreWarmedInstanceCount = utils.Int32(int32(windowsSiteConfig.PreWarmedInstanceCount)) } if metadata.ResourceData.HasChange("site_config.0.vnet_route_all_enabled") { - expanded.VnetRouteAllEnabled = utils.Bool(linuxSiteConfig.VnetRouteAllEnabled) + expanded.VnetRouteAllEnabled = utils.Bool(windowsSiteConfig.VnetRouteAllEnabled) } expanded.AppSettings = &appSettings @@ -861,6 +1520,83 @@ func FlattenSiteConfigLinuxFunctionApp(functionAppSiteConfig *web.SiteConfig) (* return result, nil } +func FlattenSiteConfigWindowsFunctionApp(functionAppSiteConfig *web.SiteConfig) (*SiteConfigWindowsFunctionApp, error) { + if functionAppSiteConfig == nil { + return nil, fmt.Errorf("flattening site config: SiteConfig was nil") + } + + result := &SiteConfigWindowsFunctionApp{ + AlwaysOn: utils.NormaliseNilableBool(functionAppSiteConfig.AlwaysOn), + AppCommandLine: utils.NormalizeNilableString(functionAppSiteConfig.AppCommandLine), + AppScaleLimit: int(utils.NormaliseNilableInt32(functionAppSiteConfig.FunctionAppScaleLimit)), + DetailedErrorLogging: utils.NormaliseNilableBool(functionAppSiteConfig.DetailedErrorLoggingEnabled), + HealthCheckPath: utils.NormalizeNilableString(functionAppSiteConfig.HealthCheckPath), + Http2Enabled: utils.NormaliseNilableBool(functionAppSiteConfig.HTTP20Enabled), + WindowsFxVersion: utils.NormalizeNilableString(functionAppSiteConfig.WindowsFxVersion), + LoadBalancing: string(functionAppSiteConfig.LoadBalancing), + ManagedPipelineMode: string(functionAppSiteConfig.ManagedPipelineMode), + NumberOfWorkers: int(utils.NormaliseNilableInt32(functionAppSiteConfig.NumberOfWorkers)), + ScmType: string(functionAppSiteConfig.ScmType), + FtpsState: string(functionAppSiteConfig.FtpsState), + RuntimeScaleMonitoring: utils.NormaliseNilableBool(functionAppSiteConfig.FunctionsRuntimeScaleMonitoringEnabled), + MinTlsVersion: string(functionAppSiteConfig.MinTLSVersion), + ScmMinTlsVersion: string(functionAppSiteConfig.ScmMinTLSVersion), + PreWarmedInstanceCount: int(utils.NormaliseNilableInt32(functionAppSiteConfig.PreWarmedInstanceCount)), + ElasticInstanceMinimum: int(utils.NormaliseNilableInt32(functionAppSiteConfig.MinimumElasticInstanceCount)), + Use32BitWorker: utils.NormaliseNilableBool(functionAppSiteConfig.Use32BitWorkerProcess), + WebSockets: utils.NormaliseNilableBool(functionAppSiteConfig.WebSocketsEnabled), + ScmUseMainIpRestriction: utils.NormaliseNilableBool(functionAppSiteConfig.ScmIPSecurityRestrictionsUseMain), + RemoteDebugging: utils.NormaliseNilableBool(functionAppSiteConfig.RemoteDebuggingEnabled), + RemoteDebuggingVersion: strings.ToUpper(utils.NormalizeNilableString(functionAppSiteConfig.RemoteDebuggingVersion)), + VnetRouteAllEnabled: utils.NormaliseNilableBool(functionAppSiteConfig.VnetRouteAllEnabled), + } + + if v := functionAppSiteConfig.APIDefinition; v != nil && v.URL != nil { + result.ApiDefinition = *v.URL + } + + if v := functionAppSiteConfig.APIManagementConfig; v != nil && v.ID != nil { + result.ApiManagementConfigId = *v.ID + } + + if functionAppSiteConfig.IPSecurityRestrictions != nil { + result.IpRestriction = FlattenIpRestrictions(functionAppSiteConfig.IPSecurityRestrictions) + } + + if functionAppSiteConfig.ScmIPSecurityRestrictions != nil { + result.ScmIpRestriction = FlattenIpRestrictions(functionAppSiteConfig.ScmIPSecurityRestrictions) + } + + if v := functionAppSiteConfig.DefaultDocuments; v != nil { + result.DefaultDocuments = *v + } + + if functionAppSiteConfig.Cors != nil { + corsSettings := functionAppSiteConfig.Cors + cors := CorsSetting{} + if corsSettings.SupportCredentials != nil { + cors.SupportCredentials = *corsSettings.SupportCredentials + } + + if corsSettings.AllowedOrigins != nil && len(*corsSettings.AllowedOrigins) != 0 { + cors.AllowedOrigins = *corsSettings.AllowedOrigins + result.Cors = []CorsSetting{cors} + } + } + + var appStack []ApplicationStackWindowsFunctionApp + if functionAppSiteConfig.WindowsFxVersion != nil { + decoded, err := DecodeFunctionAppWindowsFxVersion(*functionAppSiteConfig.WindowsFxVersion) + if err != nil { + return nil, fmt.Errorf("flattening site config: %s", err) + } + appStack = decoded + } + result.ApplicationStack = appStack + + return result, nil +} + func ParseWebJobsStorageString(input *string) (name, key string) { if input == nil { return diff --git a/internal/services/appservice/helpers/fx_strings.go b/internal/services/appservice/helpers/fx_strings.go index a5cbcf1e9740..e1412b3fa0bf 100644 --- a/internal/services/appservice/helpers/fx_strings.go +++ b/internal/services/appservice/helpers/fx_strings.go @@ -72,6 +72,9 @@ func EncodeFunctionAppLinuxFxVersion(input []ApplicationStackLinuxFunctionApp) * case appStack.JavaVersion != "": appType = "Java" appString = appStack.JavaVersion + case appStack.PowerShellCoreVersion != "": + appType = "PowerShell" + appString = appStack.PowerShellCoreVersion case len(appStack.Docker) > 0 && appStack.Docker[0].ImageName != "": appType = "Docker" dockerCfg := appStack.Docker[0] @@ -115,6 +118,10 @@ func DecodeFunctionAppLinuxFxVersion(input string) ([]ApplicationStackLinuxFunct appStack := ApplicationStackLinuxFunctionApp{JavaVersion: parts[1]} result = append(result, appStack) + case "powershell": + appStack := ApplicationStackLinuxFunctionApp{PowerShellCoreVersion: parts[1]} + result = append(result, appStack) + case "docker": // This is handled as part of unpacking the app_settings using DecodeFunctionAppDockerFxString but included here for signposting as this is not intuitive. } @@ -147,3 +154,62 @@ func DecodeFunctionAppDockerFxString(input string, partial ApplicationStackDocke return []ApplicationStackDocker{partial}, nil } + +func EncodeFunctionAppWindowsFxVersion(input []ApplicationStackWindowsFunctionApp) *string { + if len(input) == 0 { + return utils.String("") + } + + appStack := input[0] + var appType, appString string + switch { + case appStack.NodeVersion != "": + appType = "Node" + appString = appStack.NodeVersion + case appStack.DotNetVersion != "": + appType = "DotNet" + appString = appStack.DotNetVersion + case appStack.JavaVersion != "": + appType = "Java" + appString = appStack.JavaVersion + case appStack.PowerShellCoreVersion != "": + appType = "PowerShell" + appString = appStack.PowerShellCoreVersion + } + + return utils.String(fmt.Sprintf("%s|%s", appType, appString)) +} + +func DecodeFunctionAppWindowsFxVersion(input string) ([]ApplicationStackWindowsFunctionApp, error) { + if input == "" { + // This is a valid string for "Custom" stack which we picked up earlier, so we can skip here + return nil, nil + } + + parts := strings.Split(input, "|") + if len(parts) != 2 { + return nil, fmt.Errorf("unrecognised WindowsFxVersion format received, got %s", input) + } + + result := make([]ApplicationStackWindowsFunctionApp, 0) + + switch strings.ToLower(parts[0]) { + case "dotnet": + appStack := ApplicationStackWindowsFunctionApp{DotNetVersion: parts[1]} + result = append(result, appStack) + + case "node": + appStack := ApplicationStackWindowsFunctionApp{NodeVersion: parts[1]} + result = append(result, appStack) + + case "java": + appStack := ApplicationStackWindowsFunctionApp{JavaVersion: parts[1]} + result = append(result, appStack) + + case "powershell": + appStack := ApplicationStackWindowsFunctionApp{PowerShellCoreVersion: parts[1]} + result = append(result, appStack) + } + + return result, nil +} diff --git a/internal/services/appservice/helpers/web_app_schema.go b/internal/services/appservice/helpers/web_app_schema.go index 9c8bcb64e7a7..c320c1c7cdbb 100644 --- a/internal/services/appservice/helpers/web_app_schema.go +++ b/internal/services/appservice/helpers/web_app_schema.go @@ -10,7 +10,6 @@ import ( "github.com/Azure/go-autorest/autorest/date" "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" apimValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/apimanagement/validate" - msiValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/msi/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" "github.com/hashicorp/terraform-provider-azurerm/utils" @@ -145,7 +144,7 @@ func SiteConfigSchemaWindows() *pluginsdk.Schema { "container_registry_managed_identity_client_id": { Type: pluginsdk.TypeString, Optional: true, - ValidateFunc: msiValidate.UserAssignedIdentityID, + ValidateFunc: validation.IsUUID, }, "default_documents": { @@ -540,7 +539,7 @@ func SiteConfigSchemaLinux() *pluginsdk.Schema { "container_registry_managed_identity_client_id": { Type: pluginsdk.TypeString, Optional: true, - ValidateFunc: msiValidate.UserAssignedIdentityID, + ValidateFunc: validation.IsUUID, }, "default_documents": { @@ -3487,6 +3486,7 @@ func FlattenSiteConfigWindows(appSiteConfig *web.SiteConfig, currentStack string UseManagedIdentityACR: utils.NormaliseNilableBool(appSiteConfig.AcrUseManagedIdentityCreds), VirtualApplications: flattenVirtualApplications(appSiteConfig.VirtualApplications), WebSockets: utils.NormaliseNilableBool(appSiteConfig.WebSocketsEnabled), + VnetRouteAllEnabled: utils.NormaliseNilableBool(appSiteConfig.VnetRouteAllEnabled), } if appSiteConfig.APIManagementConfig != nil && appSiteConfig.APIManagementConfig.ID != nil { @@ -3682,6 +3682,7 @@ func FlattenAppSettings(input web.StringDictionary) (map[string]string, *int) { "DIAGNOSTICS_AZUREBLOBRETENTIONINDAYS", "WEBSITE_HTTPLOGGING_CONTAINER_URL", "WEBSITE_HTTPLOGGING_RETENTION_DAYS", + "WEBSITE_VNET_ROUTE_ALL", maxPingFailures, } @@ -3692,7 +3693,7 @@ func FlattenAppSettings(input web.StringDictionary) (map[string]string, *int) { healthCheckCount = &h } - // Remove the settings the service adds for legacy reasons when logging settings are specified. + // Remove the settings the service adds for legacy reasons. for _, v := range unmanagedSettings { //nolint:typecheck delete(appSettings, v) } diff --git a/internal/services/appservice/linux_function_app_resource_test.go b/internal/services/appservice/linux_function_app_resource_test.go index 3976b8a62e14..35fc444a42ef 100644 --- a/internal/services/appservice/linux_function_app_resource_test.go +++ b/internal/services/appservice/linux_function_app_resource_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "regexp" + "strings" "testing" "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" @@ -375,7 +376,7 @@ func TestAccLinuxFunctionApp_withConnectionStrings(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.builtInLogging(data, SkuStandardPlan, true), + Config: r.connectionStrings(data, SkuStandardPlan), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), @@ -765,6 +766,22 @@ func TestAccLinuxFunctionApp_appStackDockerManagedServiceIdentity(t *testing.T) }) } +func TestAccLinuxFunctionApp_appStackPowerShellCore(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_linux_function_app", "test") + r := LinuxFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appStackPowerShellCore(data, SkuBasicPlan, "7"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + ), + }, + data.ImportStep(), + }) +} + // Others func TestAccLinuxFunctionApp_updateServicePlan(t *testing.T) { @@ -870,7 +887,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -892,7 +909,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -916,7 +933,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -941,7 +958,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -968,7 +985,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -992,7 +1009,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -1019,7 +1036,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -1047,7 +1064,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -1071,7 +1088,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -1119,7 +1136,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -1153,7 +1170,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -1180,7 +1197,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -1206,7 +1223,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -1232,7 +1249,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -1258,7 +1275,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -1284,7 +1301,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -1314,7 +1331,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -1342,6 +1359,32 @@ resource "azurerm_linux_function_app" "test" { `, r.identityTemplate(data, planSku), data.RandomInteger) } +func (r LinuxFunctionAppResource) appStackPowerShellCore(data acceptance.TestData, planSku string, version string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_linux_function_app" "test" { + name = "acctest-LFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + site_config { + application_stack { + powershell_core_version = "%s" + } + } +} +`, r.template(data, planSku), data.RandomInteger, version) +} + func (r LinuxFunctionAppResource) backup(data acceptance.TestData, planSku string) string { return fmt.Sprintf(` provider "azurerm" { @@ -1351,7 +1394,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -1396,7 +1439,7 @@ resource "azurerm_application_insights" "test" { } resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%[2]d" + name = "acctest-LFA-%[2]d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -1487,7 +1530,7 @@ resource "azurerm_linux_function_app" "test" { } } load_balancing_mode = "LeastResponseTime" - remote_debugging = true + remote_debugging_enabled = true remote_debugging_version = "VS2019" scm_ip_restriction { @@ -1556,7 +1599,7 @@ resource "azurerm_application_insights" "test" { } resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%[2]d" + name = "acctest-LFA-%[2]d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -1663,7 +1706,7 @@ resource "azurerm_linux_function_app" "test" { load_balancing_mode = "LeastResponseTime" pre_warmed_instance_count = 2 - remote_debugging = true + remote_debugging_enabled = true remote_debugging_version = "VS2017" scm_ip_restriction { @@ -1716,8 +1759,21 @@ provider "azurerm" { %s +resource "azurerm_user_assigned_identity" "test" { + name = "acct-%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_application_insights" "test" { + name = "acctestappinsights-%[2]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + application_type = "web" +} + resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%[2]d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -1745,7 +1801,80 @@ resource "azurerm_linux_function_app" "test" { type = "PostgreSQL" } - site_config {} + site_config { + app_command_line = "whoami" + api_definition_url = "https://example.com/azure_function_app_def.json" + // api_management_api_id = "" // TODO + application_insights_key = azurerm_application_insights.test.instrumentation_key + application_insights_connection_string = azurerm_application_insights.test.connection_string + + application_stack { + python_version = "3.8" + } + + container_registry_use_managed_identity = true + container_registry_managed_identity_client_id = azurerm_user_assigned_identity.test.client_id + + default_documents = [ + "first.html", + "second.jsp", + "third.aspx", + "hostingstart.html", + ] + + http2_enabled = true + + ip_restriction { + ip_address = "10.10.10.10/32" + name = "test-restriction" + priority = 123 + action = "Allow" + headers { + x_azure_fdid = ["55ce4ed1-4b06-4bf1-b40e-4638452104da"] + x_fd_health_probe = ["1"] + x_forwarded_for = ["9.9.9.9/32", "2002::1234:abcd:ffff:c0a8:101/64"] + x_forwarded_host = ["example.com"] + } + } + + load_balancing_mode = "LeastResponseTime" + pre_warmed_instance_count = 2 + remote_debugging_enabled = true + remote_debugging_version = "VS2017" + + scm_ip_restriction { + ip_address = "10.20.20.20/32" + name = "test-scm-restriction" + priority = 123 + action = "Allow" + headers { + x_azure_fdid = ["55ce4ed1-4b06-4bf1-b40e-4638452104da"] + x_fd_health_probe = ["1"] + x_forwarded_for = ["9.9.9.9/32", "2002::1234:abcd:ffff:c0a8:101/64"] + x_forwarded_host = ["example.com"] + } + } + + use_32_bit_worker = true + websockets_enabled = true + ftps_state = "FtpsOnly" + health_check_path = "/health-check" + number_of_workers = 3 + + minimum_tls_version = "1.1" + scm_minimum_tls_version = "1.1" + + cors { + allowed_origins = [ + "https://www.contoso.com", + "www.contoso.com", + ] + + support_credentials = true + } + + vnet_route_all_enabled = true + } } `, r.storageContainerTemplate(data, SkuElasticPremiumPlan), data.RandomInteger) } @@ -1759,7 +1888,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.update.id @@ -1783,7 +1912,7 @@ provider "azurerm" { %s resource "azurerm_linux_function_app" "test" { - name = "acctest-FA-%d" + name = "acctest-LFA-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name service_plan_id = azurerm_service_plan.test.id @@ -1797,9 +1926,13 @@ resource "azurerm_linux_function_app" "test" { } func (LinuxFunctionAppResource) template(data acceptance.TestData, planSku string) string { + var additionalConfig string + if strings.EqualFold(planSku, "EP1") { + additionalConfig = "maximum_elastic_worker_count = 5" + } return fmt.Sprintf(` resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" + name = "acctestRG-LFA-%d" location = "%s" } @@ -1817,14 +1950,15 @@ resource "azurerm_service_plan" "test" { resource_group_name = azurerm_resource_group.test.name os_type = "Linux" sku_name = "%s" + %s } -`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, planSku) +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, planSku, additionalConfig) } func (LinuxFunctionAppResource) templateExtraStorageAccount(data acceptance.TestData, planSku string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { - name = "acctestRG-%[1]d" + name = "acctestRG-LFA-%[1]d" location = "%[2]s" } diff --git a/internal/services/appservice/registration.go b/internal/services/appservice/registration.go index d2a0dc80fb73..2251e292a654 100644 --- a/internal/services/appservice/registration.go +++ b/internal/services/appservice/registration.go @@ -42,6 +42,7 @@ func (r Registration) Resources() []sdk.Resource { LinuxWebAppResource{}, ServicePlanResource{}, WindowsWebAppResource{}, + WindowsFunctionAppResource{}, } } return []sdk.Resource{} diff --git a/internal/services/appservice/windows_function_app_resource.go b/internal/services/appservice/windows_function_app_resource.go new file mode 100644 index 000000000000..31401de3a0a6 --- /dev/null +++ b/internal/services/appservice/windows_function_app_resource.go @@ -0,0 +1,871 @@ +package appservice + +import ( + "context" + "fmt" + "strconv" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2021-02-01/web" + "github.com/google/uuid" + "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" + "github.com/hashicorp/terraform-provider-azurerm/internal/location" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/appservice/helpers" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/appservice/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/appservice/validate" + storageValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/storage/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tags" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type WindowsFunctionAppResource struct{} + +type WindowsFunctionAppModel struct { + Name string `tfschema:"name"` + ResourceGroup string `tfschema:"resource_group_name"` + Location string `tfschema:"location"` + ServicePlanId string `tfschema:"service_plan_id"` + StorageAccountName string `tfschema:"storage_account_name"` + + StorageAccountKey string `tfschema:"storage_account_access_key"` + StorageUsesMSI bool `tfschema:"storage_uses_managed_identity"` // Storage uses MSI not account key + + AppSettings map[string]string `tfschema:"app_settings"` + AuthSettings []helpers.AuthSettings `tfschema:"auth_settings"` + Backup []helpers.Backup `tfschema:"backup"` // Not supported on Dynamic or Basic plans + BuiltinLogging bool `tfschema:"builtin_logging_enabled"` + ClientCertEnabled bool `tfschema:"client_cert_enabled"` + ClientCertMode string `tfschema:"client_cert_mode"` + ConnectionStrings []helpers.ConnectionString `tfschema:"connection_string"` + DailyMemoryTimeQuota int `tfschema:"daily_memory_time_quota"` + Enabled bool `tfschema:"enabled"` + FunctionExtensionsVersion string `tfschema:"functions_extension_version"` + ForceDisableContentShare bool `tfschema:"content_share_force_disabled"` + HttpsOnly bool `tfschema:"https_only"` + Identity []helpers.Identity `tfschema:"identity"` + SiteConfig []helpers.SiteConfigWindowsFunctionApp `tfschema:"site_config"` + Tags map[string]string `tfschema:"tags"` + + // Computed + CustomDomainVerificationId string `tfschema:"custom_domain_verification_id"` + DefaultHostname string `tfschema:"default_hostname"` + Kind string `tfschema:"kind"` + OutboundIPAddresses string `tfschema:"outbound_ip_addresses"` + OutboundIPAddressList []string `tfschema:"outbound_ip_address_list"` + PossibleOutboundIPAddresses string `tfschema:"possible_outbound_ip_addresses"` + PossibleOutboundIPAddressList []string `tfschema:"possible_outbound_ip_address_list"` + + SiteCredentials []helpers.SiteCredential `tfschema:"site_credential"` +} + +var _ sdk.ResourceWithUpdate = WindowsFunctionAppResource{} + +var _ sdk.ResourceWithCustomImporter = WindowsFunctionAppResource{} + +var _ sdk.ResourceWithCustomizeDiff = WindowsFunctionAppResource{} + +func (r WindowsFunctionAppResource) ModelObject() interface{} { + return &WindowsFunctionAppModel{} +} + +func (r WindowsFunctionAppResource) ResourceType() string { + return "azurerm_windows_function_app" +} + +func (r WindowsFunctionAppResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return validate.FunctionAppID +} + +func (r WindowsFunctionAppResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.WebAppName, + Description: "Specifies the name of the Function App.", + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "location": location.Schema(), + + "service_plan_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validate.ServicePlanID, + Description: "The ID of the App Service Plan within which to create this Function App", + }, + + "storage_account_name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: storageValidate.StorageAccountName, + Description: "The backend storage account name which will be used by this Function App.", + }, + + "storage_account_access_key": { + Type: pluginsdk.TypeString, + Optional: true, + Sensitive: true, // TODO - Uncomment this + ValidateFunc: validation.NoZeroValues, + ExactlyOneOf: []string{ + "storage_uses_managed_identity", + "storage_account_access_key", + }, + Description: "The access key which will be used to access the storage account for the Function App.", + }, + + "storage_uses_managed_identity": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + ExactlyOneOf: []string{ + "storage_uses_managed_identity", + "storage_account_access_key", + }, + Description: "Should the Function App use its Managed Identity to access storage", + }, + + "app_settings": { + Type: pluginsdk.TypeMap, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + Description: "A map of key-value pairs for [App Settings](https://docs.microsoft.com/en-us/azure/azure-functions/functions-app-settings) and custom values.", + }, + + "auth_settings": helpers.AuthSettingsSchema(), + + "backup": helpers.BackupSchema(), + + "builtin_logging_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + Description: "Should built in logging be enabled. Configures `AzureWebJobsDashboard` app setting based on the configured storage setting", + }, + + "client_cert_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + Description: "Should the function app use Client Certificates", + }, + + "client_cert_mode": { + Type: pluginsdk.TypeString, + Optional: true, + Default: web.ClientCertModeOptional, + ValidateFunc: validation.StringInSlice([]string{ + string(web.ClientCertModeOptional), + string(web.ClientCertModeRequired), + string(web.ClientCertModeOptionalInteractiveUser), + }, false), + Description: "The mode of the Function App's client certificates requirement for incoming requests. Possible values are `Required`, `Optional`, and `OptionalInteractiveUser` ", + }, + + "connection_string": helpers.ConnectionStringSchema(), + + "daily_memory_time_quota": { + Type: pluginsdk.TypeInt, + Optional: true, + Default: 0, + ValidateFunc: validation.IntAtLeast(0), + Description: "The amount of memory in gigabyte-seconds that your application is allowed to consume per day. Setting this value only affects function apps in Consumption Plans.", + }, + + "enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + Description: "Is the Windows Function App enabled.", + }, + + "content_share_force_disabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + Description: "Force disable the content share settings.", + }, + + "functions_extension_version": { + Type: pluginsdk.TypeString, + Optional: true, + Default: "~4", + Description: "The runtime version associated with the Function App.", + }, + + "https_only": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + Description: "Can the Function App only be accessed via HTTPS?", + }, + + "identity": helpers.IdentitySchema(), + + "site_config": helpers.SiteConfigSchemaWindowsFunctionApp(), + + "tags": tags.Schema(), + } +} + +func (r WindowsFunctionAppResource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "custom_domain_verification_id": { + Type: pluginsdk.TypeString, + Computed: true, + Sensitive: true, + }, + + "default_hostname": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "kind": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "outbound_ip_addresses": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "outbound_ip_address_list": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "possible_outbound_ip_addresses": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "possible_outbound_ip_address_list": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "site_credential": helpers.SiteCredentialSchema(), + } +} + +func (r WindowsFunctionAppResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + var functionApp WindowsFunctionAppModel + + if err := metadata.Decode(&functionApp); err != nil { + return err + } + + client := metadata.Client.AppService.WebAppsClient + aseClient := metadata.Client.AppService.AppServiceEnvironmentClient + servicePlanClient := metadata.Client.AppService.ServicePlanClient + subscriptionId := metadata.Client.Account.SubscriptionId + + id := parse.NewFunctionAppID(subscriptionId, functionApp.ResourceGroup, functionApp.Name) + + servicePlanId, err := parse.ServicePlanID(functionApp.ServicePlanId) + if err != nil { + return err + } + + servicePlan, err := servicePlanClient.Get(ctx, servicePlanId.ResourceGroup, servicePlanId.ServerfarmName) + if err != nil { + return fmt.Errorf("reading %s: %+v", servicePlanId, err) + } + + sendContentSettings := !functionApp.ForceDisableContentShare + if planSku := servicePlan.Sku; planSku != nil && planSku.Tier != nil { + switch tier := *planSku.Tier; strings.ToLower(tier) { + case "dynamic": + case "elastic": + case "basic": + sendContentSettings = false + case "standard": + sendContentSettings = false + case "premiumv2", "premiumv3": + sendContentSettings = false + } + } else { + return fmt.Errorf("determining plan type for Windows %s: %v", id, err) + } + + existing, err := client.Get(ctx, id.ResourceGroup, id.SiteName) + if err != nil && !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for presence of existing Windows %s: %+v", id, err) + } + + if !utils.ResponseWasNotFound(existing.Response) { + return metadata.ResourceRequiresImport(r.ResourceType(), id) + } + + availabilityRequest := web.ResourceNameAvailabilityRequest{ + Name: utils.String(functionApp.Name), + Type: web.CheckNameResourceTypesMicrosoftWebsites, + } + + if ase := servicePlan.HostingEnvironmentProfile; ase != nil { + // Attempt to check the ASE for the appropriate suffix for the name availability request. + // This varies between internal and external ASE Types, and potentially has other names in other clouds + // We use the "internal" as the fallback here, if we can read the ASE, we'll get the full one + nameSuffix := "appserviceenvironment.net" + if ase.ID != nil { + aseId, err := parse.AppServiceEnvironmentID(*ase.ID) + nameSuffix = fmt.Sprintf("%s.%s", aseId.HostingEnvironmentName, nameSuffix) + if err != nil { + metadata.Logger.Warnf("could not parse App Service Environment ID determine FQDN for name availability check, defaulting to `%s.%s.appserviceenvironment.net`", functionApp.Name, servicePlanId) + } else { + existingASE, err := aseClient.Get(ctx, aseId.ResourceGroup, aseId.HostingEnvironmentName) + if err != nil { + metadata.Logger.Warnf("could not read App Service Environment to determine FQDN for name availability check, defaulting to `%s.%s.appserviceenvironment.net`", functionApp.Name, servicePlanId) + } else if props := existingASE.AppServiceEnvironment; props != nil && props.DNSSuffix != nil && *props.DNSSuffix != "" { + nameSuffix = *props.DNSSuffix + } + } + } + + availabilityRequest.Name = utils.String(fmt.Sprintf("%s.%s", functionApp.Name, nameSuffix)) + availabilityRequest.IsFqdn = utils.Bool(true) + } + + checkName, err := client.CheckNameAvailability(ctx, availabilityRequest) + if err != nil { + return fmt.Errorf("checking name availability for Windows %s: %+v", id, err) + } + if checkName.NameAvailable != nil && !*checkName.NameAvailable { + return fmt.Errorf("the Site Name %q failed the availability check: %+v", id.SiteName, *checkName.Message) + } + + storageString := functionApp.StorageAccountName + if !functionApp.StorageUsesMSI { + storageString = fmt.Sprintf(helpers.StorageStringFmt, functionApp.StorageAccountName, functionApp.StorageAccountKey, metadata.Client.Account.Environment.StorageEndpointSuffix) + } + siteConfig, err := helpers.ExpandSiteConfigWindowsFunctionApp(functionApp.SiteConfig, nil, metadata, functionApp.FunctionExtensionsVersion, storageString, functionApp.StorageUsesMSI) + if err != nil { + return fmt.Errorf("expanding site_config for Windows %s: %+v", id, err) + } + + if functionApp.BuiltinLogging { + if functionApp.AppSettings == nil { + functionApp.AppSettings = make(map[string]string) + } + if !functionApp.StorageUsesMSI { + functionApp.AppSettings["AzureWebJobsDashboard"] = storageString + } else { + functionApp.AppSettings["AzureWebJobsDashboard__accountName"] = functionApp.StorageAccountName + } + } + if sendContentSettings { + if functionApp.AppSettings == nil { + functionApp.AppSettings = make(map[string]string) + } + suffix := uuid.New().String()[0:4] + functionApp.AppSettings["WEBSITE_CONTENTSHARE"] = fmt.Sprintf("%s-%s", strings.ToLower(functionApp.Name), suffix) + functionApp.AppSettings["WEBSITE_CONTENTAZUREFILECONNECTIONSTRING"] = storageString + } + + siteConfig.WindowsFxVersion = helpers.EncodeFunctionAppWindowsFxVersion(functionApp.SiteConfig[0].ApplicationStack) + siteConfig.AppSettings = helpers.MergeUserAppSettings(siteConfig.AppSettings, functionApp.AppSettings) + + siteEnvelope := web.Site{ + Location: utils.String(functionApp.Location), + Tags: tags.FromTypedObject(functionApp.Tags), + Kind: utils.String("functionapp"), + Identity: helpers.ExpandIdentity(functionApp.Identity), + SiteProperties: &web.SiteProperties{ + ServerFarmID: utils.String(functionApp.ServicePlanId), + Enabled: utils.Bool(functionApp.Enabled), + HTTPSOnly: utils.Bool(functionApp.HttpsOnly), + SiteConfig: siteConfig, + ClientCertEnabled: utils.Bool(functionApp.ClientCertEnabled), + ClientCertMode: web.ClientCertMode(functionApp.ClientCertMode), + DailyMemoryTimeQuota: utils.Int32(int32(functionApp.DailyMemoryTimeQuota)), + }, + } + + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.SiteName, siteEnvelope) + if err != nil { + return fmt.Errorf("creating Windows %s: %+v", id, err) + } + + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for creation of Windows %s: %+v", id, err) + } + + updateFuture, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.SiteName, siteEnvelope) + if err != nil { + return fmt.Errorf("updating properties of Windows %s: %+v", id, err) + } + if err := updateFuture.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for creation of Windows %s: %+v", id, err) + } + + backupConfig := helpers.ExpandBackupConfig(functionApp.Backup) + if backupConfig.BackupRequestProperties != nil { + if _, err := client.UpdateBackupConfiguration(ctx, id.ResourceGroup, id.SiteName, *backupConfig); err != nil { + return fmt.Errorf("adding Backup Settings for Windows %s: %+v", id, err) + } + } + + auth := helpers.ExpandAuthSettings(functionApp.AuthSettings) + if auth.SiteAuthSettingsProperties != nil { + if _, err := client.UpdateAuthSettings(ctx, id.ResourceGroup, id.SiteName, *auth); err != nil { + return fmt.Errorf("setting Authorisation Settings for Windows %s: %+v", id, err) + } + } + + connectionStrings := helpers.ExpandConnectionStrings(functionApp.ConnectionStrings) + if connectionStrings.Properties != nil { + if _, err := client.UpdateConnectionStrings(ctx, id.ResourceGroup, id.SiteName, *connectionStrings); err != nil { + return fmt.Errorf("setting Connection Strings for Windows %s: %+v", id, err) + } + } + + if _, ok := metadata.ResourceData.GetOk("site_config.0.app_service_logs"); ok { + appServiceLogs := helpers.ExpandFunctionAppAppServiceLogs(functionApp.SiteConfig[0].AppServiceLogs) + if _, err := client.UpdateDiagnosticLogsConfig(ctx, id.ResourceGroup, id.SiteName, appServiceLogs); err != nil { + return fmt.Errorf("updating App Service Log Settings for %s: %+v", id, err) + } + } + + metadata.SetID(id) + return nil + }, + } +} + +func (r WindowsFunctionAppResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 25 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.AppService.WebAppsClient + id, err := parse.FunctionAppID(metadata.ResourceData.Id()) + if err != nil { + return err + } + functionApp, err := client.Get(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + if utils.ResponseWasNotFound(functionApp.Response) { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("reading Windows %s: %+v", id, err) + } + + if functionApp.SiteProperties == nil { + return fmt.Errorf("reading properties of Windows %s", id) + } + props := *functionApp.SiteProperties + + appSettingsResp, err := client.ListApplicationSettings(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + return fmt.Errorf("reading App Settings for Windows %s: %+v", id, err) + } + + connectionStrings, err := client.ListConnectionStrings(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + return fmt.Errorf("reading Connection String information for Windows %s: %+v", id, err) + } + + siteCredentialsFuture, err := client.ListPublishingCredentials(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + return fmt.Errorf("listing Site Publishing Credential information for Windows %s: %+v", id, err) + } + + if err := siteCredentialsFuture.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for Site Publishing Credential information for Windows %s: %+v", id, err) + } + siteCredentials, err := siteCredentialsFuture.Result(*client) + if err != nil { + return fmt.Errorf("reading Site Publishing Credential information for Windows %s: %+v", id, err) + } + + auth, err := client.GetAuthSettings(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + return fmt.Errorf("reading Auth Settings for Windows %s: %+v", id, err) + } + + backup, err := client.GetBackupConfiguration(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + if !utils.ResponseWasNotFound(backup.Response) { + return fmt.Errorf("reading Backup Settings for Windows %s: %+v", id, err) + } + } + + logs, err := client.GetDiagnosticLogsConfiguration(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + return fmt.Errorf("reading logs configuration for Windows %s: %+v", id, err) + } + + state := WindowsFunctionAppModel{ + Name: id.SiteName, + ResourceGroup: id.ResourceGroup, + ServicePlanId: utils.NormalizeNilableString(props.ServerFarmID), + Location: location.NormalizeNilable(functionApp.Location), + Enabled: utils.NormaliseNilableBool(functionApp.Enabled), + ClientCertMode: string(functionApp.ClientCertMode), + DailyMemoryTimeQuota: int(utils.NormaliseNilableInt32(props.DailyMemoryTimeQuota)), + Tags: tags.ToTypedObject(functionApp.Tags), + Kind: utils.NormalizeNilableString(functionApp.Kind), + } + + if identity := helpers.FlattenIdentity(functionApp.Identity); identity != nil { + state.Identity = identity + } + + configResp, err := client.GetConfiguration(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + return fmt.Errorf("making Read request on AzureRM Function App Configuration %q: %+v", id.SiteName, err) + } + + siteConfig, err := helpers.FlattenSiteConfigWindowsFunctionApp(configResp.SiteConfig) + if err != nil { + return fmt.Errorf("reading Site Config for Windows %s: %+v", id, err) + } + state.SiteConfig = []helpers.SiteConfigWindowsFunctionApp{*siteConfig} + + state.unpackWindowsFunctionAppSettings(appSettingsResp) + + state.ConnectionStrings = helpers.FlattenConnectionStrings(connectionStrings) + + state.SiteCredentials = helpers.FlattenSiteCredentials(siteCredentials) + + state.AuthSettings = helpers.FlattenAuthSettings(auth) + + state.Backup = helpers.FlattenBackupConfig(backup) + + state.SiteConfig[0].AppServiceLogs = helpers.FlattenFunctionAppAppServiceLogs(logs) + + state.HttpsOnly = utils.NormaliseNilableBool(functionApp.HTTPSOnly) + state.ClientCertEnabled = utils.NormaliseNilableBool(functionApp.ClientCertEnabled) + + return metadata.Encode(&state) + }, + } +} + +func (r WindowsFunctionAppResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.AppService.WebAppsClient + id, err := parse.FunctionAppID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + metadata.Logger.Infof("deleting Windows %s", *id) + + deleteMetrics := true + deleteEmptyServerFarm := false + if _, err := client.Delete(ctx, id.ResourceGroup, id.SiteName, &deleteMetrics, &deleteEmptyServerFarm); err != nil { + return fmt.Errorf("deleting Windows %s: %+v", id, err) + } + return nil + }, + } +} + +func (r WindowsFunctionAppResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.AppService.WebAppsClient + + id, err := parse.FunctionAppID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + var state WindowsFunctionAppModel + if err := metadata.Decode(&state); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + existing, err := client.Get(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + return fmt.Errorf("reading Windows %s: %v", id, err) + } + + // Some service plan updates are allowed - see customiseDiff for exceptions + if metadata.ResourceData.HasChange("service_plan_id") { + existing.SiteProperties.ServerFarmID = utils.String(state.ServicePlanId) + } + + if metadata.ResourceData.HasChange("enabled") { + existing.SiteProperties.Enabled = utils.Bool(state.Enabled) + } + + if metadata.ResourceData.HasChange("https_only") { + existing.SiteProperties.HTTPSOnly = utils.Bool(state.HttpsOnly) + } + + if metadata.ResourceData.HasChange("client_cert_enabled") { + existing.SiteProperties.ClientCertEnabled = utils.Bool(state.ClientCertEnabled) + } + + if metadata.ResourceData.HasChange("client_cert_mode") { + existing.SiteProperties.ClientCertMode = web.ClientCertMode(state.ClientCertMode) + } + + if metadata.ResourceData.HasChange("identity") { + existing.Identity = helpers.ExpandIdentity(state.Identity) + } + + if metadata.ResourceData.HasChange("tags") { + existing.Tags = tags.FromTypedObject(state.Tags) + } + + storageString := "" + if !state.StorageUsesMSI { + storageString = fmt.Sprintf(helpers.StorageStringFmt, state.StorageAccountName, state.StorageAccountKey, metadata.Client.Account.Environment.StorageEndpointSuffix) + } + + // Note: We process this regardless to give us a "clean" view of service-side app_settings, so we can reconcile the user-defined entries later + siteConfig, err := helpers.ExpandSiteConfigWindowsFunctionApp(state.SiteConfig, existing.SiteConfig, metadata, state.FunctionExtensionsVersion, storageString, state.StorageUsesMSI) + if state.BuiltinLogging { + if state.AppSettings == nil && !state.StorageUsesMSI { + state.AppSettings = make(map[string]string) + } + state.AppSettings["AzureWebJobsDashboard"] = storageString + } + + if metadata.ResourceData.HasChange("site_config") { + if err != nil { + return fmt.Errorf("expanding Site Config for Windows %s: %+v", id, err) + } + existing.SiteConfig = siteConfig + } + + if metadata.ResourceData.HasChange("site_config.0.application_stack") { + existing.SiteConfig.WindowsFxVersion = helpers.EncodeFunctionAppWindowsFxVersion(state.SiteConfig[0].ApplicationStack) + } + + existing.SiteConfig.AppSettings = helpers.MergeUserAppSettings(siteConfig.AppSettings, state.AppSettings) + + updateFuture, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.SiteName, existing) + if err != nil { + return fmt.Errorf("updating Windows %s: %+v", id, err) + } + if err := updateFuture.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting to update %s: %+v", id, err) + } + + if _, err := client.UpdateConfiguration(ctx, id.ResourceGroup, id.SiteName, web.SiteConfigResource{SiteConfig: siteConfig}); err != nil { + return fmt.Errorf("updating Site Config for Windows %s: %+v", id, err) + } + + if metadata.ResourceData.HasChange("connection_string") { + connectionStringUpdate := helpers.ExpandConnectionStrings(state.ConnectionStrings) + if connectionStringUpdate.Properties == nil { + connectionStringUpdate.Properties = map[string]*web.ConnStringValueTypePair{} + } + if _, err := client.UpdateConnectionStrings(ctx, id.ResourceGroup, id.SiteName, *connectionStringUpdate); err != nil { + return fmt.Errorf("updating Connection Strings for Windows %s: %+v", id, err) + } + } + + if metadata.ResourceData.HasChange("auth_settings") { + authUpdate := helpers.ExpandAuthSettings(state.AuthSettings) + if _, err := client.UpdateAuthSettings(ctx, id.ResourceGroup, id.SiteName, *authUpdate); err != nil { + return fmt.Errorf("updating Auth Settings for Windows %s: %+v", id, err) + } + } + + if metadata.ResourceData.HasChange("backup") { + backupUpdate := helpers.ExpandBackupConfig(state.Backup) + if backupUpdate.BackupRequestProperties == nil { + if _, err := client.DeleteBackupConfiguration(ctx, id.ResourceGroup, id.SiteName); err != nil { + return fmt.Errorf("removing Backup Settings for Windows %s: %+v", id, err) + } + } else { + if _, err := client.UpdateBackupConfiguration(ctx, id.ResourceGroup, id.SiteName, *backupUpdate); err != nil { + return fmt.Errorf("updating Backup Settings for Windows %s: %+v", id, err) + } + } + } + + if metadata.ResourceData.HasChange("site_config.0.app_service_logs") { + appServiceLogs := helpers.ExpandFunctionAppAppServiceLogs(state.SiteConfig[0].AppServiceLogs) + if _, err := client.UpdateDiagnosticLogsConfig(ctx, id.ResourceGroup, id.SiteName, appServiceLogs); err != nil { + return fmt.Errorf("updating App Service Log Settings for %s: %+v", id, err) + } + } + + return nil + }, + } +} + +func (r WindowsFunctionAppResource) CustomImporter() sdk.ResourceRunFunc { + return func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.AppService.WebAppsClient + servicePlanClient := metadata.Client.AppService.ServicePlanClient + + id, err := parse.FunctionAppID(metadata.ResourceData.Id()) + if err != nil { + return err + } + site, err := client.Get(ctx, id.ResourceGroup, id.SiteName) + if err != nil || site.SiteProperties == nil { + return fmt.Errorf("reading Windows %s: %+v", id, err) + } + props := site.SiteProperties + if props.ServerFarmID == nil { + return fmt.Errorf("determining Service Plan ID for Windows %s: %+v", id, err) + } + servicePlanId, err := parse.ServicePlanID(*props.ServerFarmID) + if err != nil { + return err + } + + sp, err := servicePlanClient.Get(ctx, servicePlanId.ResourceGroup, servicePlanId.ServerfarmName) + if err != nil || sp.Kind == nil { + return fmt.Errorf("reading Service Plan for Windows %s: %+v", id, err) + } + if strings.Contains(strings.ToLower(*sp.Kind), "linux") || !(strings.Contains(strings.ToLower(*sp.Kind), "elastic") || strings.Contains(strings.ToLower(*sp.Kind), "functionapp") || strings.Contains(strings.ToLower(*sp.Kind), "app")) { + return fmt.Errorf("specified Service Plan is not a Windows Functionapp plan") + } + + return nil + } +} + +func (r WindowsFunctionAppResource) CustomizeDiff() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.AppService.ServicePlanClient + rd := metadata.ResourceDiff + + if rd.HasChange("service_plan_id") { + currentPlanIdRaw, newPlanIdRaw := rd.GetChange("service_plan_id") + if newPlanIdRaw.(string) == "" { + // Plans creating a new service_plan inline will be empty as `Computed` known after apply + return nil + } + newPlanId, err := parse.ServicePlanID(newPlanIdRaw.(string)) + if err != nil { + return fmt.Errorf("reading new plan id %+v", err) + } + + var currentTierIsDynamic, newTierIsDynamic, newTierIsBasic bool + + newPlan, err := client.Get(ctx, newPlanId.ResourceGroup, newPlanId.ServerfarmName) + if err != nil { + return fmt.Errorf("could not read new Service Plan to check tier %s: %+v", newPlanId, err) + } + if planSku := newPlan.Sku; planSku != nil { + if tier := planSku.Tier; tier != nil { + newTierIsDynamic = strings.EqualFold(*tier, "dynamic") + newTierIsBasic = strings.EqualFold(*tier, "basic") + } + } + + // Service Plans can only be updated in place when both New and Existing are not Dynamic + if currentPlanIdRaw.(string) != "" { + currentPlanId, err := parse.ServicePlanID(currentPlanIdRaw.(string)) + if err != nil { + return fmt.Errorf("reading existing plan id %+v", err) + } + + currentPlan, err := client.Get(ctx, currentPlanId.ResourceGroup, currentPlanId.ServerfarmName) + if err != nil { + return fmt.Errorf("could not read current Service Plan to check tier %s: %+v", currentPlanId, err) + } + + if planSku := currentPlan.Sku; planSku != nil { + if tier := planSku.Tier; tier != nil { + currentTierIsDynamic = strings.EqualFold(*tier, "dynamic") + } + } + + if currentTierIsDynamic || newTierIsDynamic { + if err := rd.ForceNew("service_plan_id"); err != nil { + return err + } + } + } + if _, ok := rd.GetOk("backup"); ok && newTierIsDynamic { + return fmt.Errorf("cannot specify backup configuration for Dynamic tier Service Plans, Standard or higher is required") + } + if _, ok := rd.GetOk("backup"); ok && newTierIsBasic { + return fmt.Errorf("cannot specify backup configuration for Basic tier Service Plans, Standard or higher is required") + } + } + return nil + }, + } +} + +func (m *WindowsFunctionAppModel) unpackWindowsFunctionAppSettings(input web.StringDictionary) { + if input.Properties == nil { + return + } + + appSettings := make(map[string]string) + var dockerSettings helpers.ApplicationStackDocker + m.BuiltinLogging = false + + for k, v := range input.Properties { + switch k { + case "FUNCTIONS_EXTENSION_VERSION": + m.FunctionExtensionsVersion = utils.NormalizeNilableString(v) + + case "WEBSITE_NODE_DEFAULT_VERSION": // Note - This is only set if it's not the default of 12, but we collect it from WindowsFxVersion so can discard it here + case "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": + case "WEBSITE_CONTENTSHARE": + case "WEBSITE_HTTPLOGGING_RETENTION_DAYS": + case "FUNCTIONS_WORKER_RUNTIME": + if m.SiteConfig[0].ApplicationStack != nil { + m.SiteConfig[0].ApplicationStack[0].CustomHandler = strings.EqualFold(*v, "custom") + } + + case "DOCKER_REGISTRY_SERVER_URL": + dockerSettings.RegistryURL = utils.NormalizeNilableString(v) + + case "DOCKER_REGISTRY_SERVER_USERNAME": + dockerSettings.RegistryUsername = utils.NormalizeNilableString(v) + + case "DOCKER_REGISTRY_SERVER_PASSWORD": + dockerSettings.RegistryPassword = utils.NormalizeNilableString(v) + + case "APPINSIGHTS_INSTRUMENTATIONKEY": + m.SiteConfig[0].AppInsightsInstrumentationKey = utils.NormalizeNilableString(v) + + case "APPLICATIONINSIGHTS_CONNECTION_STRING": + m.SiteConfig[0].AppInsightsConnectionString = utils.NormalizeNilableString(v) + + case "AzureWebJobsStorage": + m.StorageAccountName, m.StorageAccountKey = helpers.ParseWebJobsStorageString(v) + + case "AzureWebJobsDashboard": + m.BuiltinLogging = true + + case "WEBSITE_HEALTHCHECK_MAXPINGFAILURES": + i, _ := strconv.Atoi(utils.NormalizeNilableString(v)) + m.SiteConfig[0].HealthCheckEvictionTime = utils.NormaliseNilableInt(&i) + + default: + appSettings[k] = utils.NormalizeNilableString(v) + } + } + + m.AppSettings = appSettings +} diff --git a/internal/services/appservice/windows_function_app_resource_test.go b/internal/services/appservice/windows_function_app_resource_test.go new file mode 100644 index 000000000000..83eacf2fa65c --- /dev/null +++ b/internal/services/appservice/windows_function_app_resource_test.go @@ -0,0 +1,1851 @@ +package appservice_test + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/appservice/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type WindowsFunctionAppResource struct{} + +// Plan types +func TestAccWindowsFunctionApp_basicBasicPlan(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data, SkuBasicPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_basicConsumptionPlan(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data, SkuConsumptionPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_basicElasticPremiumPlan(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data, SkuElasticPremiumPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_basicPremiumAppServicePlan(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data, SkuPremiumPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_basicStandardPlan(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +// App Settings by Plan Type + +func TestAccWindowsFunctionApp_withAppSettingsBasic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appSettings(data, SkuBasicPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + check.That(data.ResourceName).Key("app_settings.%").HasValue("2"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_withAppSettingsConsumption(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appSettings(data, SkuConsumptionPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + check.That(data.ResourceName).Key("app_settings.%").HasValue("2"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_withAppSettingsElasticPremiumPlan(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appSettings(data, SkuElasticPremiumPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + check.That(data.ResourceName).Key("app_settings.%").HasValue("2"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_withAppSettingsPremiumPlan(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appSettings(data, SkuPremiumPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + check.That(data.ResourceName).Key("app_settings.%").HasValue("2"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_withAppSettingsStandardPlan(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appSettings(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + check.That(data.ResourceName).Key("app_settings.%").HasValue("2"), + ), + }, + data.ImportStep(), + }) +} + +// backup by plan type + +func TestAccWindowsFunctionApp_withBackupElasticPremiumPlan(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.backup(data, SkuElasticPremiumPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_withBackupPremiumPlan(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.backup(data, SkuPremiumPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_withBackupStandardPlan(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.backup(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +// Completes by plan type + +func TestAccWindowsFunctionApp_consumptionComplete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.consumptionComplete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_consumptionCompleteUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data, SkuConsumptionPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + { + Config: r.consumptionComplete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data, SkuConsumptionPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + }) +} + +func TestAccWindowsFunctionApp_elasticPremiumComplete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.elasticComplete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_standardComplete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.standardComplete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +// Individual Settings / Blocks + +func TestAccWindowsFunctionApp_withAuthSettingsConsumption(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withAuthSettings(data, SkuConsumptionPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_withAuthSettingsStandard(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withAuthSettings(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_builtInLogging(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.builtInLogging(data, SkuStandardPlan, true), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_withConnectionStrings(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.connectionStrings(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_withUserIdentity(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.userIdentity(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_withConnectionStringsUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + { + Config: r.connectionStrings(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + { + Config: r.connectionStringsUpdate(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + { + Config: r.basic(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_dailyTimeQuotaConsumptionPlan(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.dailyTimeLimitQuota(data, SkuConsumptionPlan, 1000), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_dailyTimeQuotaElasticPremiumPlan(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.dailyTimeLimitQuota(data, SkuElasticPremiumPlan, 2000), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_healthCheckPath(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.healthCheckPath(data, "S1"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_healthCheckPathWithEviction(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.healthCheckPathWithEviction(data, "S1"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_healthCheckPathWithEvictionUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data, "S1"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.healthCheckPathWithEviction(data, "S1"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data, "S1"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_appServiceLogging(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appServiceLogs(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_appServiceLoggingUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + { + Config: r.appServiceLogs(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + { + Config: r.basic(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +// App Stacks + +func TestAccWindowsFunctionApp_appStackDotNet31(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appStackDotNet(data, SkuBasicPlan, "3.1"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_appStackDotNet6(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appStackDotNet(data, SkuBasicPlan, "6"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_appStackNode(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appStackNode(data, SkuBasicPlan, "14"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_appStackNodeUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appStackNode(data, SkuBasicPlan, "12"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + { + Config: r.appStackNode(data, SkuBasicPlan, "14"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_appStackJava(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appStackJava(data, SkuBasicPlan, "11"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_appStackJavaUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appStackJava(data, SkuBasicPlan, "8"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), { + Config: r.appStackJava(data, SkuBasicPlan, "11"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_appStackPowerShellCore(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appStackPowerShellCore(data, SkuBasicPlan, "7"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +// Others + +func TestAccWindowsFunctionApp_updateServicePlan(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + { + Config: r.servicePlanUpdate(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_updateStorageAccount(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + { + Config: r.updateStorageAccount(data, SkuStandardPlan), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +// Exists + +func (r WindowsFunctionAppResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.FunctionAppID(state.ID) + if err != nil { + return nil, err + } + + resp, err := client.AppService.WebAppsClient.Get(ctx, id.ResourceGroup, id.SiteName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return utils.Bool(false), nil + } + return nil, fmt.Errorf("retrieving Windows Web App %s: %+v", id, err) + } + if utils.ResponseWasNotFound(resp.Response) { + return utils.Bool(false), nil + } + return utils.Bool(true), nil +} + +// Configs + +func (r WindowsFunctionAppResource) basic(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + site_config {} +} +`, r.template(data, planSku), data.RandomInteger) +} + +func (r WindowsFunctionAppResource) appSettings(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + app_settings = { + foo = "bar" + secret = "sauce" + } + + site_config {} +} +`, r.template(data, planSku), data.RandomInteger) +} + +func (r WindowsFunctionAppResource) backup(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + backup { + name = "acctest" + storage_account_url = "https://${azurerm_storage_account.test.name}.blob.core.windows.net/${azurerm_storage_container.test.name}${data.azurerm_storage_account_sas.test.sas}&sr=b" + schedule { + frequency_interval = 7 + frequency_unit = "Day" + } + } + + site_config {} +} +`, r.storageContainerTemplate(data, planSku), data.RandomInteger) +} + +func (r WindowsFunctionAppResource) consumptionComplete(data acceptance.TestData) string { + planSku := "Y1" + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_user_assigned_identity" "test" { + name = "acct-%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_application_insights" "test" { + name = "acctestappinsights-%[2]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + application_type = "web" +} + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%[2]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + app_settings = { + foo = "bar" + secret = "sauce" + } + + auth_settings { + enabled = true + issuer = "https://sts.windows.net/%[3]s" + + additional_login_parameters = { + test_key = "test_value" + } + + active_directory { + client_id = "aadclientid" + client_secret = "aadsecret" + + allowed_audiences = [ + "activedirectorytokenaudiences", + ] + } + + facebook { + app_id = "facebookappid" + app_secret = "facebookappsecret" + + oauth_scopes = [ + "facebookscope", + ] + } + } + + builtin_logging_enabled = false + client_cert_enabled = true + client_cert_mode = "Required" + + connection_string { + name = "Second" + value = "some-postgresql-connection-string" + type = "PostgreSQL" + } + + enabled = false + functions_extension_version = "~3" + https_only = true + + identity { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.test.id] + } + + site_config { + app_command_line = "whoami" + api_definition_url = "https://example.com/azure_function_app_def.json" + app_scale_limit = 3 + // api_management_api_id = "" // TODO + application_insights_key = azurerm_application_insights.test.instrumentation_key + application_insights_connection_string = azurerm_application_insights.test.connection_string + + default_documents = [ + "first.html", + "second.jsp", + "third.aspx", + "hostingstart.html", + ] + + http2_enabled = true + ip_restriction { + ip_address = "10.10.10.10/32" + name = "test-restriction" + priority = 123 + action = "Allow" + headers { + x_azure_fdid = ["55ce4ed1-4b06-4bf1-b40e-4638452104da"] + x_fd_health_probe = ["1"] + x_forwarded_for = ["9.9.9.9/32", "2002::1234:abcd:ffff:c0a8:101/64"] + x_forwarded_host = ["example.com"] + } + } + load_balancing_mode = "LeastResponseTime" + remote_debugging_enabled = true + remote_debugging_version = "VS2019" + + scm_ip_restriction { + ip_address = "10.20.20.20/32" + name = "test-scm-restriction" + priority = 123 + action = "Allow" + headers { + x_azure_fdid = ["55ce4ed1-4b06-4bf1-b40e-4638452104da"] + x_fd_health_probe = ["1"] + x_forwarded_for = ["9.9.9.9/32", "2002::1234:abcd:ffff:c0a8:101/64"] + x_forwarded_host = ["example.com"] + } + } + + use_32_bit_worker = true + websockets_enabled = true + ftps_state = "FtpsOnly" + health_check_path = "/health-check" + + application_stack { + powershell_core_version = "7" + } + + minimum_tls_version = "1.1" + scm_minimum_tls_version = "1.1" + + cors { + allowed_origins = [ + "https://www.contoso.com", + "www.contoso.com", + ] + + support_credentials = true + } + } + + tags = { + terraform = "true" + Env = "AccTest" + } +} +`, r.template(data, planSku), data.RandomInteger, data.Client().TenantID) +} + +func (r WindowsFunctionAppResource) standardComplete(data acceptance.TestData) string { + planSku := "S1" + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_user_assigned_identity" "test" { + name = "acct-%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_application_insights" "test" { + name = "acctestappinsights-%[2]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + application_type = "web" +} + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%[2]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + app_settings = { + foo = "bar" + secret = "sauce" + } + + auth_settings { + enabled = true + issuer = "https://sts.windows.net/%[3]s" + + additional_login_parameters = { + test_key = "test_value" + } + + active_directory { + client_id = "aadclientid" + client_secret = "aadsecret" + + allowed_audiences = [ + "activedirectorytokenaudiences", + ] + } + + facebook { + app_id = "facebookappid" + app_secret = "facebookappsecret" + + oauth_scopes = [ + "facebookscope", + ] + } + } + + backup { + name = "acctest" + storage_account_url = "https://${azurerm_storage_account.test.name}.blob.core.windows.net/${azurerm_storage_container.test.name}${data.azurerm_storage_account_sas.test.sas}&sr=b" + schedule { + frequency_interval = 7 + frequency_unit = "Day" + } + } + + builtin_logging_enabled = false + client_cert_enabled = true + client_cert_mode = "OptionalInteractiveUser" + + connection_string { + name = "First" + value = "some-postgresql-connection-string" + type = "PostgreSQL" + } + + enabled = false + functions_extension_version = "~3" + https_only = true + + identity { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.test.id] + } + + site_config { + always_on = true + app_command_line = "whoami" + api_definition_url = "https://example.com/azure_function_app_def.json" + // api_management_api_id = "" // TODO + application_insights_key = azurerm_application_insights.test.instrumentation_key + application_insights_connection_string = azurerm_application_insights.test.connection_string + + application_stack { + powershell_core_version = "7" + } + + default_documents = [ + "first.html", + "second.jsp", + "third.aspx", + "hostingstart.html", + ] + + http2_enabled = true + + ip_restriction { + ip_address = "10.10.10.10/32" + name = "test-restriction" + priority = 123 + action = "Allow" + headers { + x_azure_fdid = ["55ce4ed1-4b06-4bf1-b40e-4638452104da"] + x_fd_health_probe = ["1"] + x_forwarded_for = ["9.9.9.9/32", "2002::1234:abcd:ffff:c0a8:101/64"] + x_forwarded_host = ["example.com"] + } + } + + load_balancing_mode = "LeastResponseTime" + pre_warmed_instance_count = 2 + remote_debugging_enabled = true + remote_debugging_version = "VS2017" + + scm_ip_restriction { + ip_address = "10.20.20.20/32" + name = "test-scm-restriction" + priority = 123 + action = "Allow" + headers { + x_azure_fdid = ["55ce4ed1-4b06-4bf1-b40e-4638452104da"] + x_fd_health_probe = ["1"] + x_forwarded_for = ["9.9.9.9/32", "2002::1234:abcd:ffff:c0a8:101/64"] + x_forwarded_host = ["example.com"] + } + } + + use_32_bit_worker = true + websockets_enabled = true + ftps_state = "FtpsOnly" + health_check_path = "/health-check" + number_of_workers = 3 + + minimum_tls_version = "1.1" + scm_minimum_tls_version = "1.1" + + cors { + allowed_origins = [ + "https://www.contoso.com", + "www.contoso.com", + ] + + support_credentials = true + } + + vnet_route_all_enabled = true + } + + tags = { + terraform = "true" + Env = "AccTest" + } +} +`, r.storageContainerTemplate(data, planSku), data.RandomInteger, data.Client().TenantID) +} + +func (r WindowsFunctionAppResource) elasticComplete(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_application_insights" "test" { + name = "acctestappinsights-%[2]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + application_type = "web" +} + + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%[2]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + app_settings = { + foo = "bar" + secret = "sauce" + } + + backup { + name = "acctest" + storage_account_url = "https://${azurerm_storage_account.test.name}.blob.core.windows.net/${azurerm_storage_container.test.name}${data.azurerm_storage_account_sas.test.sas}&sr=b" + schedule { + frequency_interval = 7 + frequency_unit = "Day" + } + } + + connection_string { + name = "Example" + value = "some-postgresql-connection-string" + type = "PostgreSQL" + } + + site_config { + app_command_line = "whoami" + api_definition_url = "https://example.com/azure_function_app_def.json" + application_insights_key = azurerm_application_insights.test.instrumentation_key + application_insights_connection_string = azurerm_application_insights.test.connection_string + + application_stack { + powershell_core_version = "7" + } + + default_documents = [ + "first.html", + "second.jsp", + "third.aspx", + "hostingstart.html", + ] + + http2_enabled = true + + ip_restriction { + ip_address = "10.10.10.10/32" + name = "test-restriction" + priority = 123 + action = "Allow" + headers { + x_azure_fdid = ["55ce4ed1-4b06-4bf1-b40e-4638452104da"] + x_fd_health_probe = ["1"] + x_forwarded_for = ["9.9.9.9/32", "2002::1234:abcd:ffff:c0a8:101/64"] + x_forwarded_host = ["example.com"] + } + } + + load_balancing_mode = "LeastResponseTime" + pre_warmed_instance_count = 2 + remote_debugging_enabled = true + remote_debugging_version = "VS2017" + + scm_ip_restriction { + ip_address = "10.20.20.20/32" + name = "test-scm-restriction" + priority = 123 + action = "Allow" + headers { + x_azure_fdid = ["55ce4ed1-4b06-4bf1-b40e-4638452104da"] + x_fd_health_probe = ["1"] + x_forwarded_for = ["9.9.9.9/32", "2002::1234:abcd:ffff:c0a8:101/64"] + x_forwarded_host = ["example.com"] + } + } + + use_32_bit_worker = true + websockets_enabled = true + ftps_state = "FtpsOnly" + health_check_path = "/health-check" + number_of_workers = 3 + + minimum_tls_version = "1.1" + scm_minimum_tls_version = "1.1" + + cors { + allowed_origins = [ + "https://www.contoso.com", + "www.contoso.com", + ] + + support_credentials = true + } + + vnet_route_all_enabled = true + } +} +`, r.storageContainerTemplate(data, SkuElasticPremiumPlan), data.RandomInteger) +} + +func (r WindowsFunctionAppResource) withAuthSettings(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + auth_settings { + enabled = true + issuer = "https://sts.windows.net/%s" + runtime_version = "1.0" + unauthenticated_client_action = "RedirectToLoginPage" + token_refresh_extension_hours = 75 + token_store_enabled = true + + additional_login_parameters = { + test_key = "test_value" + } + + allowed_external_redirect_urls = [ + "https://terra.form", + ] + + active_directory { + client_id = "aadclientid" + client_secret = "aadsecret" + + allowed_audiences = [ + "activedirectorytokenaudiences", + ] + } + } + + site_config {} +} +`, r.template(data, planSku), data.RandomInteger, data.RandomString) +} + +func (r WindowsFunctionAppResource) builtInLogging(data acceptance.TestData, planSku string, builtInLogging bool) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + builtin_logging_enabled = %t + + site_config {} +} +`, r.template(data, planSku), data.RandomInteger, builtInLogging) +} + +func (r WindowsFunctionAppResource) connectionStrings(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + connection_string { + name = "Example" + value = "some-postgresql-connection-string" + type = "PostgreSQL" + } + + site_config {} +} +`, r.template(data, planSku), data.RandomInteger) +} + +func (r WindowsFunctionAppResource) userIdentity(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + site_config {} + + identity { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.test.id] + } +} +`, r.identityTemplate(data, planSku), data.RandomInteger) +} + +func (r WindowsFunctionAppResource) connectionStringsUpdate(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + connection_string { + name = "Example" + value = "some-postgresql-connection-string" + type = "PostgreSQL" + } + + connection_string { + name = "AnotherExample" + value = "some-other-connection-string" + type = "Custom" + } + + site_config {} +} +`, r.template(data, planSku), data.RandomInteger) +} + +func (r WindowsFunctionAppResource) dailyTimeLimitQuota(data acceptance.TestData, planSku string, quota int) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + daily_memory_time_quota = %d + + site_config {} +} +`, r.template(data, planSku), data.RandomInteger, quota) +} + +func (r WindowsFunctionAppResource) healthCheckPath(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + site_config { + health_check_path = "/health" + } +} +`, r.template(data, planSku), data.RandomInteger) +} + +func (r WindowsFunctionAppResource) healthCheckPathWithEviction(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + site_config { + health_check_path = "/health" + health_check_eviction_time_in_min = 3 + } +} +`, r.template(data, planSku), data.RandomInteger) +} + +func (r WindowsFunctionAppResource) appServiceLogs(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + site_config { + app_service_logs { + disk_quota_mb = 25 + retention_period_days = 7 + } + } +} +`, r.template(data, planSku), data.RandomInteger) +} + +func (r WindowsFunctionAppResource) appStackDotNet(data acceptance.TestData, planSku string, version string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + site_config { + application_stack { + dotnet_version = "%s" + } + } +} +`, r.template(data, planSku), data.RandomInteger, version) +} + +func (r WindowsFunctionAppResource) appStackNode(data acceptance.TestData, planSku string, nodeVersion string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + site_config { + application_stack { + node_version = "%s" + } + } +} +`, r.template(data, planSku), data.RandomInteger, nodeVersion) +} + +func (r WindowsFunctionAppResource) appStackJava(data acceptance.TestData, planSku string, javaVersion string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + site_config { + application_stack { + java_version = "%s" + } + } +} +`, r.template(data, planSku), data.RandomInteger, javaVersion) +} + +func (r WindowsFunctionAppResource) appStackPowerShellCore(data acceptance.TestData, planSku string, version string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + site_config { + application_stack { + powershell_core_version = "%s" + } + } +} +`, r.template(data, planSku), data.RandomInteger, version) +} + +func (r WindowsFunctionAppResource) servicePlanUpdate(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctest-WFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.update.id + + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + site_config {} + + depends_on = [azurerm_service_plan.update] +} +`, r.templateServicePlanUpdate(data, planSku), data.RandomInteger) +} + +func (r WindowsFunctionAppResource) updateStorageAccount(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_function_app" "test" { + name = "acctest-LFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + storage_account_name = azurerm_storage_account.update.name + storage_account_access_key = azurerm_storage_account.update.primary_access_key + + site_config {} +} +`, r.templateExtraStorageAccount(data, planSku), data.RandomInteger) +} + +// Config Templates + +func (WindowsFunctionAppResource) template(data acceptance.TestData, planSku string) string { + var additionalConfig string + if strings.EqualFold(planSku, "EP1") { + additionalConfig = "maximum_elastic_worker_count = 5" + } + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-LFA-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + os_type = "Windows" + sku_name = "%s" + %s +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, planSku, additionalConfig) +} + +func (r WindowsFunctionAppResource) storageContainerTemplate(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +%s + +resource "azurerm_storage_container" "test" { + name = "test" + storage_account_name = azurerm_storage_account.test.name + container_access_type = "private" +} + +data "azurerm_storage_account_sas" "test" { + connection_string = azurerm_storage_account.test.primary_connection_string + https_only = true + + resource_types { + service = false + container = false + object = true + } + + services { + blob = true + queue = false + table = false + file = false + } + + start = "2021-04-01" + expiry = "2024-03-30" + + permissions { + read = false + write = true + delete = false + list = false + add = false + create = false + update = false + process = false + } +} + +`, r.template(data, planSku)) +} + +func (r WindowsFunctionAppResource) identityTemplate(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +%s + +resource "azurerm_user_assigned_identity" "test" { + name = "acct-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +`, r.template(data, planSku), data.RandomInteger) +} + +func (r WindowsFunctionAppResource) templateServicePlanUpdate(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +%s + +resource "azurerm_service_plan" "update" { + name = "acctestASP2-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + os_type = "Windows" + sku_name = "%s" +} +`, r.template(data, planSku), data.RandomInteger, planSku) +} + +func (WindowsFunctionAppResource) templateExtraStorageAccount(data acceptance.TestData, planSku string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-WFA-%[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_storage_account" "update" { + name = "acctestsa2%[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_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + os_type = "Windows" + sku_name = "%[4]s" +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, planSku) +} diff --git a/internal/services/appservice/windows_web_app_resource_test.go b/internal/services/appservice/windows_web_app_resource_test.go index 23b2bc93e6da..439e70b7df79 100644 --- a/internal/services/appservice/windows_web_app_resource_test.go +++ b/internal/services/appservice/windows_web_app_resource_test.go @@ -1594,7 +1594,7 @@ resource "azurerm_windows_web_app" "test" { } container_registry_use_managed_identity = true - container_registry_managed_identity_client_id = azurerm_user_assigned_identity.test.id + container_registry_managed_identity_client_id = azurerm_user_assigned_identity.test.client_id // auto_swap_slot_name = // TODO auto_heal = true @@ -2232,7 +2232,7 @@ resource "azurerm_windows_web_app" "test" { site_config { container_registry_use_managed_identity = true - container_registry_managed_identity_client_id = azurerm_user_assigned_identity.test.id + container_registry_managed_identity_client_id = azurerm_user_assigned_identity.test.client_id } } `, r.baseTemplate(data), data.RandomInteger) diff --git a/website/docs/r/linux_function_app.html.markdown b/website/docs/r/linux_function_app.html.markdown index 8919c4032c6a..fe8a72b4b80b 100644 --- a/website/docs/r/linux_function_app.html.markdown +++ b/website/docs/r/linux_function_app.html.markdown @@ -132,6 +132,8 @@ A `application_stack` block supports the following: * `python_version` - (Optional) The version of Python to run. Possible values include `3.6`, `3.7`, `3.8`, and `3.9`. +* `powershell_core_version` - (Optional) The version of PowerShell Core to run. Possible values are `7`. + * `use_custom_runtime` - (Optional) Should the Linux Function App use a custom runtime? --- @@ -411,7 +413,7 @@ A `site_config` block supports the following: * `pre_warmed_instance_count` - (Optional) The number of pre-warmed instances for this function app. Only affects apps on an Elastic Premium plan. -* `remote_debugging` - (Optional) Should Remote Debugging be enabled. Defaults to `false`. +* `remote_debugging_enabled` - (Optional) Should Remote Debugging be enabled. Defaults to `false`. * `remote_debugging_version` - (Optional) The Remote Debugging Version. Possible values include `VS2017` and `VS2019`. @@ -476,7 +478,7 @@ The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/d * `create` - (Defaults to 30 minutes) Used when creating the Linux Function App. * `read` - (Defaults to 25 minutes) Used when retrieving the Linux Function App. * `update` - (Defaults to 30 minutes) Used when updating the Linux Function App. -* `delete` - (Defaults to 5 minutes) Used when deleting the Linux Function App. +* `delete` - (Defaults to 30 minutes) Used when deleting the Linux Function App. ## Import diff --git a/website/docs/r/windows_function_app.html.markdown b/website/docs/r/windows_function_app.html.markdown new file mode 100644 index 000000000000..63e7cb38f2ad --- /dev/null +++ b/website/docs/r/windows_function_app.html.markdown @@ -0,0 +1,463 @@ +--- +subcategory: "App Service (Web Apps)" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_windows_function_app" +description: |- + Manages a Windows Function App. +--- + +# azurerm_windows_function_app + +Manages a Windows Function App. + +!> **Note:** This Resource is coming in version 3.0 of the Azure Provider and is available **as an opt-in Beta** - more information can be found in [the upcoming version 3.0 of the Azure Provider](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/3.0-overview). + +## Example Usage + +```hcl +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_storage_account" "example" { + name = "windowsfunctionappsa" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_service_plan" "example" { + name = "example-app-service-plan" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + os_type = "Windows" + sku_name = "Y1" +} + +resource "azurerm_windows_function_app" "example" { + name = "example-windows-function-app" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + + storage_account_name = azurerm_storage_account.example.name + service_plan_id = azurerm_service_plan.example.id + + site_config {} +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `location` - (Required) The Azure Region where the Windows Function App should exist. Changing this forces a new Windows Function App to be created. + +* `name` - (Required) The name which should be used for this Windows Function App. Changing this forces a new Windows Function App to be created. + +* `resource_group_name` - (Required) The name of the Resource Group where the Windows Function App should exist. Changing this forces a new Windows Function App to be created. + +* `service_plan_id` - (Required) The ID of the App Service Plan within which to create this Function App. + +* `site_config` - (Required) A `site_config` block as defined below. + +* `storage_account_name` - (Required) The backend storage account name which will be used by this Function App. + +--- + +* `app_settings` - (Optional) A map of key-value pairs for [App Settings](https://docs.microsoft.com/en-us/azure/azure-functions/functions-app-settings) and custom values. + +* `auth_settings` - (Optional) A `auth_settings` block as defined below. + +* `backup` - (Optional) A `backup` block as defined below. + +* `builtin_logging_enabled` - (Optional) Should built in logging be enabled. Configures `AzureWebJobsDashboard` app setting based on the configured storage setting. + +* `client_cert_enabled` - (Optional) Should the function app use Client Certificates. + +* `client_cert_mode` - (Optional) The mode of the Function App's client certificates requirement for incoming requests. Possible values are `Required`, `Optional`, and `OptionalInteractiveUser`. + +* `connection_string` - (Optional) One or more `connection_string` blocks as defined below. + +* `daily_memory_time_quota` - (Optional) The amount of memory in gigabyte-seconds that your application is allowed to consume per day. Setting this value only affects function apps under the consumption plan. Defaults to `0`. + +* `enabled` - (Optional) Is the Function App enabled? + +* `force_disable_content_share` - (Optional) Should the settings for linking the Function App to storage be suppressed. + +* `functions_extension_version` - (Optional) The runtime version associated with the Function App. Defaults to `~4`. + +* `https_only` - (Optional) Can the Function App only be accessed via HTTPS? Defaults to `false`. + +* `identity` - (Optional) A `identity` block as defined below. + +* `storage_account_access_key` - (Optional) The access key which will be used to access the backend storage account for the Function App. Conflicts with `storage_uses_managed_identity`. + +* `storage_uses_managed_identity` - (Optional) Should the Function App use Managed Identity to access the storage account. Conflicts with `storage_account_access_key`. + +~> **NOTE:** One of `storage_account_access_key` or `storage_uses_managed_identity` must be specified. + +* `tags` - (Optional) A mapping of tags which should be assigned to the Windows Function App. + +--- + +An `active_directory` block supports the following: + +* `client_id` - (Required) The ID of the Client to use to authenticate with Azure Active Directory. + +* `allowed_audiences` - (Optional) Specifies a list of Allowed audience values to consider when validating JWTs issued by Azure Active Directory. + +~> **Note:** The `client_id` value is always considered an allowed audience. + +* `client_secret` - (Optional) The Client Secret for the Client ID. Cannot be used with `client_secret_setting_name`. + +* `client_secret_setting_name` - (Optional) The App Setting name that contains the client secret of the Client. Cannot be used with `client_secret`. + +--- + +A `application_stack` block supports the following: + +* `dotnet_version` - (Optional) The version of .Net to use. Possible values include `3.1` and `6`. + +* `java_version` - (Optional) The Version of Java to use. Supported versions include `8`, and `11`. + +* `node_version` - (Optional) The version of Node to run. Possible values include `12`, and `14`. + +* `powershell_core_version` - (Optional) The version of PowerShell Core to run. Possible values are `7`. + +* `use_custom_runtime` - (Optional) Should the Windows Function App use a custom runtime? + +--- + +An `app_service_logs` block supports the following: + +* `disk_quota_mb` - (Required) The amount of disk space to use for logs. Valid values are between `25` and `100`. + +* `retention_period_days` - (Optional) The retention period for logs in days. Valid values are between `0` and `99999`. Defaults to `0` (never delete). + +~> **NOTE:** This block is not supported on Consumption plans. + +--- + +An `auth_settings` block supports the following: + +* `enabled` - (Required) Should the Authentication / Authorization feature be enabled for the Windows Web App? + +* `active_directory` - (Optional) An `active_directory` block as defined above. + +* `additional_login_params` - (Optional) Specifies a map of Login Parameters to send to the OpenID Connect authorization endpoint when a user logs in. + +* `allowed_external_redirect_urls` - (Optional) Specifies a list of External URLs that can be redirected to as part of logging in or logging out of the Windows Web App. + +* `default_provider` - (Optional) The default authentication provider to use when multiple providers are configured. Possible values include: `AzureActiveDirectory`, `Facebook`, `Google`, `MicrosoftAccount`, `Twitter`, `Github` + +~> **NOTE:** This setting is only needed if multiple providers are configured, and the `unauthenticated_client_action` is set to "RedirectToLoginPage". + +* `facebook` - (Optional) A `facebook` block as defined below. + +* `github` - (Optional) A `github` block as defined below. + +* `google` - (Optional) A `google` block as defined below. + +* `issuer` - (Optional) The OpenID Connect Issuer URI that represents the entity which issues access tokens for this Windows Web App. + +~> **NOTE:** When using Azure Active Directory, this value is the URI of the directory tenant, e.g. https://sts.windows.net/{tenant-guid}/. + +* `microsoft` - (Optional) A `microsoft` block as defined below. + +* `runtime_version` - (Optional) The RuntimeVersion of the Authentication / Authorization feature in use for the Windows Web App. + +* `token_refresh_extension_hours` - (Optional) The number of hours after session token expiration that a session token can be used to call the token refresh API. Defaults to `72` hours. + +* `token_store_enabled` - (Optional) Should the Windows Web App durably store platform-specific security tokens that are obtained during login flows? Defaults to `false`. + +* `twitter` - (Optional) A `twitter` block as defined below. + +* `unauthenticated_client_action` - (Optional) The action to take when an unauthenticated client attempts to access the app. Possible values include: `RedirectToLoginPage`, `AllowAnonymous`. + +--- + +A `backup` block supports the following: + +* `name` - (Required) The name which should be used for this Backup. + +* `schedule` - (Required) A `schedule` block as defined below. + +* `storage_account_url` - (Required) The SAS URL to the container. + +* `enabled` - (Optional) Should this backup job be enabled? + +--- + +A `connection_string` block supports the following: + +* `name` - (Required) The name which should be used for this Connection. + +* `type` - (Required) Type of database. Possible values include: `MySQL`, `SQLServer`, `SQLAzure`, `Custom`, `NotificationHub`, `ServiceBus`, `EventHub`, `APIHub`, `DocDb`, `RedisCache`, and `PostgreSQL`. + +* `value` - (Required) The connection string value. + + +--- + +A `cors` block supports the following: + +* `allowed_origins` - (Required) Specifies a list of origins that should be allowed to make cross-origin calls. + +* `support_credentials` - (Optional) Are credentials allowed in CORS requests? Defaults to `false`. + +--- + +A `facebook` block supports the following: + +* `app_id` - (Required) The App ID of the Facebook app used for login. + +* `app_secret` - (Optional) The App Secret of the Facebook app used for Facebook Login. Cannot be specified with `app_secret_setting_name`. + +* `app_secret_setting_name` - (Optional) The app setting name that contains the `app_secret` value used for Facebook Login. Cannot be specified with `app_secret`. + +* `oauth_scopes` - (Optional) Specifies a list of OAuth 2.0 scopes to be requested as part of Facebook Login authentication. + +--- + +A `github` block supports the following: + +* `client_id` - (Required) The ID of the GitHub app used for login. + +* `client_secret` - (Optional) The Client Secret of the GitHub app used for GitHub Login. Cannot be specified with `client_secret_setting_name`. + +* `client_secret_setting_name` - (Optional) The app setting name that contains the `client_secret` value used for GitHub Login. Cannot be specified with `client_secret`. + +* `oauth_scopes` - (Optional) Specifies a list of OAuth 2.0 scopes that will be requested as part of GitHub Login authentication. + +--- + +A `google` block supports the following: + +* `client_id` - (Required) The OpenID Connect Client ID for the Google web application. + +* `client_secret` - (Optional) The client secret associated with the Google web application. Cannot be specified with `client_secret_setting_name`. + +* `client_secret_setting_name` - (Optional) The app setting name that contains the `client_secret` value used for Google Login. Cannot be specified with `client_secret`. + +* `oauth_scopes` - (Optional) Specifies a list of OAuth 2.0 scopes that will be requested as part of Google Sign-In authentication. If not specified, "openid", "profile", and "email" are used as default scopes. + +--- + +A `headers` block supports the following: + +~> **NOTE:** Please see the [official Azure Documentation](https://docs.microsoft.com/en-us/azure/app-service/app-service-ip-restrictions#filter-by-http-header) for details on using header filtering. + +* `x_azure_fdid` - (Optional) Specifies a list of Azure Front Door IDs. + +* `x_fd_health_probe` - (Optional) Specifies if a Front Door Health Probe should be expected. + +* `x_forwarded_for` - (Optional) Specifies a list of addresses for which matching should be applied. Omitting this value means allow any. + +* `x_forwarded_host` - (Optional) Specifies a list of Hosts for which matching should be applied. + +--- + +A `identity` block supports the following: + +* `type` - (Required) The type of managed service identity. Possible values include: `SystemAssigned`, `UserAssigned`, and `SystemAssigned, UserAssigned`. + +* `identity_ids` - (Optional) Specifies a list of User Assigned Identity IDs. + +--- + +An `ip_restriction` block supports the following: + +* `action` - (Optional) The action to take. Possible values are `Allow` or `Deny`. + +* `headers` - (Optional) A `headers` block as defined above. + +* `ip_address` - (Optional) The CIDR notation of the IP or IP Range to match. For example: `10.0.0.0/24` or `192.168.10.1/32` + +* `name` - (Optional) The name which should be used for this `ip_restriction`. + +* `priority` - (Optional) The priority value of this `ip_restriction`. + +* `service_tag` - (Optional) The Service Tag used for this IP Restriction. + +* `virtual_network_subnet_id` - (Optional) The Virtual Network Subnet ID used for this IP Restriction. + +~> **NOTE:** One and only one of `ip_address`, `service_tag` or `virtual_network_subnet_id` must be specified. + +--- + +A `microsoft` block supports the following: + +* `client_id` - (Required) The OAuth 2.0 client ID that was created for the app used for authentication. + +* `client_secret` - (Optional) The OAuth 2.0 client secret that was created for the app used for authentication. Cannot be specified with `client_secret_setting_name`. + +* `client_secret_setting_name` - (Optional) The app setting name containing the OAuth 2.0 client secret that was created for the app used for authentication. Cannot be specified with `client_secret`. + +* `oauth_scopes` - (Optional) Specifies a list of OAuth 2.0 scopes that will be requested as part of Microsoft Account authentication. If not specified, `wl.basic` is used as the default scope. + +--- + +A `schedule` block supports the following: + +* `frequency_interval` - (Required) How often the backup should be executed (e.g. for weekly backup, this should be set to `7` and `frequency_unit` should be set to `Day`). + +~> **NOTE:** Not all intervals are supported on all Windows Function App SKU's. Please refer to the official documentation for appropriate values. + +* `frequency_unit` - (Required) The unit of time for how often the backup should take place. Possible values include: `Day` and `Hour`. + +* `keep_at_least_one_backup` - (Optional) Should the service keep at least one backup, regardless of age of backup. Defaults to `false`. + +* `retention_period_days` - (Optional) After how many days backups should be deleted. + +* `start_time` - (Optional) When the schedule should start working in RFC-3339 format. + +--- + +A `scm_ip_restriction` block supports the following: + +* `action` - (Optional) The action to take. Possible values are `Allow` or `Deny`. + +* `headers` - (Optional) A `headers` block as defined above. + +* `ip_address` - (Optional) The CIDR notation of the IP or IP Range to match. For example: `10.0.0.0/24` or `192.168.10.1/32` + +* `name` - (Optional) The name which should be used for this `ip_restriction`. + +* `priority` - (Optional) The priority value of this `ip_restriction`. + +* `service_tag` - (Optional) The Service Tag used for this IP Restriction. + +* `virtual_network_subnet_id` - (Optional) The Virtual Network Subnet ID used for this IP Restriction. + +~> **NOTE:** One and only one of `ip_address`, `service_tag` or `virtual_network_subnet_id` must be specified. + +--- + +A `site_config` block supports the following: + +* `always_on` - (Optional) If this Windows Web App is Always On enabled. Defaults to `false`. + +* `api_definition_url` - (Optional) The URL of the API definition that describes this Windows Function App. + +* `api_management_api_id` - (Optional) The ID of the API Management API for this Windows Function App. + +* `app_command_line` - (Optional) The App command line to launch. + +* `app_scale_limit` - (Optional) The number of workers this function app can scale out to. Only applicable to apps on the Consumption and Premium plan. + +* `application_insights_connection_string` - (Optional) The Connection String for linking the Windows Function App to Application Insights. + +* `application_insights_key` - (Optional) The Instrumentation Key for connecting the Windows Function App to Application Insights. + +* `application_stack` - (Optional) An `application_stack` block as defined above. + +* `app_service_logs` - (Optional) An `app_service_logs` block as defined above. + +* `auto_swap_slot_name` - (Optional) The Windows Function App Slot Name to automatically swap to when deployment to that slot is successfully completed. + +* `cors` - (Optional) A `cors` block as defined above. + +* `default_documents` - (Optional) Specifies a list of Default Documents for the Windows Web App. + +* `elastic_instance_minimum` - (Optional) The number of minimum instances for this Windows Function App. Only affects apps on Elastic Premium plans. + +* `ftps_state` - (Optional) State of FTP / FTPS service for this function app. Possible values include: `AllAllowed`, `FtpsOnly` and `Disabled`. Defaults to `Disabled`. + +* `health_check_path` - (Optional) The path to be checked for this function app health. + +* `health_check_eviction_time_in_min` - (Optional) The amount of time in minutes that a node can be unhealthy before being removed from the load balancer. Possible values are between `2` and `10`. Only valid in conjunction with `health_check_path`. + +* `http2_enabled` - (Optional) Specifies if the http2 protocol should be enabled. Defaults to `false`. + +* `ip_restriction` - (Optional) One or more `ip_restriction` blocks as defined above. + +* `load_balancing_mode` - (Optional) The Site load balancing mode. Possible values include: `WeightedRoundRobin`, `LeastRequests`, `LeastResponseTime`, `WeightedTotalTraffic`, `RequestHash`, `PerSiteRoundRobin`. Defaults to `LeastRequests` if omitted. + +* `managed_pipeline_mode` - (Optional) Managed pipeline mode. Possible values include: `Integrated`, `Classic`. Defaults to `Integrated`. + +* `minimum_tls_version` - (Optional) The configures the minimum version of TLS required for SSL requests. Possible values include: `1.0`, `1.1`, and `1.2`. Defaults to `1.2`. + +* `number_of_workers` - (Optional) The number of Workers for this Windows Function App. + +* `pre_warmed_instance_count` - (Optional) The number of pre-warmed instances for this function app. Only affects apps on an Elastic Premium plan. + +* `remote_debugging_enabled` - (Optional) Should Remote Debugging be enabled. Defaults to `false`. + +* `remote_debugging_version` - (Optional) The Remote Debugging Version. Possible values include `VS2017` and `VS2019`. + +* `runtime_scale_monitoring_enabled` - (Optional) Should Scale Monitoring of the Functions Runtime be enabled? + +* `scm_ip_restriction` - (Optional) One or more `scm_ip_restriction` blocks as defined above. + +* `scm_minimum_tls_version` - (Optional) Configures the minimum version of TLS required for SSL requests to the SCM site Possible values include: `1.0`, `1.1`, and `1.2`. Defaults to `1.2`. + +* `scm_use_main_ip_restriction` - (Optional) Should the Windows Function App `ip_restriction` configuration be used for the SCM also. + +* `use_32_bit_worker` - (Optional) Should the Windows Web App use a 32-bit worker process. Defaults to `true`. + +* `vnet_route_all_enabled` - (Optional) Should all outbound traffic to have Virtual Network Security Groups and User Defined Routes applied? Defaults to `false`. + +* `websockets_enabled` - (Optional) Should Web Sockets be enabled. Defaults to `false`. + +--- + +A `twitter` block supports the following: + +* `consumer_key` - (Required) The OAuth 1.0a consumer key of the Twitter application used for sign-in. + +* `consumer_secret` - (Optional) The OAuth 1.0a consumer secret of the Twitter application used for sign-in. Cannot be specified with `consumer_secret_setting_name`. + +* `consumer_secret_setting_name` - (Optional) The app setting name that contains the OAuth 1.0a consumer secret of the Twitter application used for sign-in. Cannot be specified with `consumer_secret`. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Windows Function App. + +* `custom_domain_verification_id` - The identifier used by App Service to perform domain ownership verification via DNS TXT record. + +* `default_hostname` - The default hostname of the Windows Function App. + +* `kind` - The Kind value for this Windows Function App. + +* `outbound_ip_address_list` - A list of outbound IP addresses. For example `["52.23.25.3", "52.143.43.12"]` + +* `outbound_ip_addresses` - A comma separated list of outbound IP addresses as a string. For example `52.23.25.3,52.143.43.12`. + +* `possible_outbound_ip_address_list` - A list of possible outbound IP addresses, not all of which are necessarily in use. This is a superset of `outbound_ip_address_list`. For example `["52.23.25.3", "52.143.43.12"]`. + +* `possible_outbound_ip_addresses` - A comma separated list of possible outbound IP addresses as a string. For example `52.23.25.3,52.143.43.12,52.143.43.17`. This is a superset of `outbound_ip_addresses`. For example `["52.23.25.3", "52.143.43.12","52.143.43.17"]`. + +* `site_credential` - A `site_credential` block as defined below. + +--- + +A `site_credential` block exports the following: + +* `name` - The Site Credentials Username used for publishing. + +* `password` - The Site Credentials Password used for publishing. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Windows Function App. +* `read` - (Defaults to 25 minutes) Used when retrieving the Windows Function App. +* `update` - (Defaults to 30 minutes) Used when updating the Windows Function App. +* `delete` - (Defaults to 30 minutes) Used when deleting the Windows Function App. + +## Import + +Windows Function Apps can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_windows_function_app.example /subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/sites/site1 +```