diff --git a/internal/services/appservice/helpers/app_stack.go b/internal/services/appservice/helpers/app_stack.go new file mode 100644 index 000000000000..3ba7a68f84f0 --- /dev/null +++ b/internal/services/appservice/helpers/app_stack.go @@ -0,0 +1,660 @@ +package helpers + +import ( + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +const ( + JavaContainerEmbeddedServer string = "JAVA" + JavaContainerTomcat string = "TOMCAT" + JavaContainerEmbeddedServerVersion string = "SE" + PhpVersionSevenPointOne string = "7.1" + PhpVersionSevenPointFour string = "7.4" + PhpVersionOff string = "Off" + + CurrentStackDotNet string = "dotnet" + CurrentStackDotNetCore string = "dotnetcore" + CurrentStackJava string = "java" + CurrentStackNode string = "node" + CurrentStackPhp string = "php" + CurrentStackPython string = "python" + + LinuxJavaServerJava string = "JAVA" + LinuxJavaServerTomcat string = "TOMCAT" + LinuxJavaServerJboss string = "JBOSSEAP" +) + +type ApplicationStackWindows struct { + CurrentStack string `tfschema:"current_stack"` + DockerContainerName string `tfschema:"docker_container_name"` + DockerContainerRegistry string `tfschema:"docker_container_registry"` + DockerContainerTag string `tfschema:"docker_container_tag"` + JavaContainer string `tfschema:"java_container"` + JavaContainerVersion string `tfschema:"java_container_version"` + JavaEmbeddedServer bool `tfschema:"java_embedded_server_enabled"` + JavaVersion string `tfschema:"java_version"` + NetFrameworkVersion string `tfschema:"dotnet_version"` + NetCoreVersion string `tfschema:"dotnet_core_version"` + NodeVersion string `tfschema:"node_version"` + PhpVersion string `tfschema:"php_version"` + PythonVersion string `tfschema:"python_version"` + Python bool `tfschema:"python"` + TomcatVersion string `tfschema:"tomcat_version"` +} + +func windowsApplicationStackSchema() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "dotnet_version": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ // Note: DotNet versions are abstracted between API and Portal displayed values, so do not match 1:1. A table of the converted values is provided in the resource doc. + "v2.0", + "v3.0", + "v4.0", + "v5.0", + "v6.0", + "v7.0"}, false), + AtLeastOneOf: []string{ + "site_config.0.application_stack.0.docker_container_name", + "site_config.0.application_stack.0.dotnet_version", + "site_config.0.application_stack.0.dotnet_core_version", + "site_config.0.application_stack.0.java_version", + "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.php_version", + "site_config.0.application_stack.0.python", + "site_config.0.application_stack.0.python_version", + }, + }, + + "dotnet_core_version": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "v4.0", + }, false), + AtLeastOneOf: []string{ + "site_config.0.application_stack.0.docker_container_name", + "site_config.0.application_stack.0.dotnet_version", + "site_config.0.application_stack.0.dotnet_core_version", + "site_config.0.application_stack.0.java_version", + "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.php_version", + "site_config.0.application_stack.0.python", + "site_config.0.application_stack.0.python_version", + }, + Description: "The version of DotNetCore to use.", + }, + + "php_version": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + PhpVersionSevenPointOne, // Deprecated + PhpVersionSevenPointFour, // Deprecated + PhpVersionOff, // Portal displays `Off` for `""` meaning use latest available + }, false), + AtLeastOneOf: []string{ + "site_config.0.application_stack.0.docker_container_name", + "site_config.0.application_stack.0.dotnet_version", + "site_config.0.application_stack.0.dotnet_core_version", + "site_config.0.application_stack.0.java_version", + "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.php_version", + "site_config.0.application_stack.0.python", + "site_config.0.application_stack.0.python_version", + }, + }, + + "python_version": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + Deprecated: "This property is deprecated. Values set are not used by the service.", + AtLeastOneOf: []string{ + "site_config.0.application_stack.0.docker_container_name", + "site_config.0.application_stack.0.dotnet_version", + "site_config.0.application_stack.0.dotnet_core_version", + "site_config.0.application_stack.0.java_version", + "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.php_version", + "site_config.0.application_stack.0.python", + "site_config.0.application_stack.0.python_version", + }, + ConflictsWith: []string{ + "site_config.0.application_stack.0.python", + }, + }, + + "python": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + AtLeastOneOf: []string{ + "site_config.0.application_stack.0.docker_container_name", + "site_config.0.application_stack.0.dotnet_version", + "site_config.0.application_stack.0.dotnet_core_version", + "site_config.0.application_stack.0.java_version", + "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.php_version", + "site_config.0.application_stack.0.python_version", + "site_config.0.application_stack.0.python", + }, + ConflictsWith: []string{ + "site_config.0.application_stack.0.python_version", + }, + }, + + "node_version": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "~12", // TODO - Remove in 4.0 due to service Deprecation. + "~14", + "~16", + "~18", + }, false), + AtLeastOneOf: []string{ + "site_config.0.application_stack.0.docker_container_name", + "site_config.0.application_stack.0.dotnet_version", + "site_config.0.application_stack.0.dotnet_core_version", + "site_config.0.application_stack.0.java_version", + "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.php_version", + "site_config.0.application_stack.0.python_version", + "site_config.0.application_stack.0.python", + }, + }, + + "java_version": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + AtLeastOneOf: []string{ + "site_config.0.application_stack.0.docker_container_name", + "site_config.0.application_stack.0.dotnet_version", + "site_config.0.application_stack.0.dotnet_core_version", + "site_config.0.application_stack.0.java_version", + "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.php_version", + "site_config.0.application_stack.0.python_version", + "site_config.0.application_stack.0.python", + }, + }, + + "java_embedded_server_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + ConflictsWith: []string{ + "site_config.0.application_stack.0.tomcat_version", + }, + RequiredWith: []string{ + "site_config.0.application_stack.0.java_version", + }, + Description: "Should the application use the embedded web server for the version of Java in use.", + }, + + "tomcat_version": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, // This is a long list of regularly changing values, not all valid values of which are made known in the portal/docs + ConflictsWith: []string{ + "site_config.0.application_stack.0.java_embedded_server_enabled", + }, + RequiredWith: []string{ + "site_config.0.application_stack.0.java_version", + }, + }, + + "java_container": { + Type: pluginsdk.TypeString, + Optional: true, + Deprecated: "this property has been deprecated in favour of `tomcat_version` and `java_embedded_server_enabled`", + ValidateFunc: validation.StringInSlice([]string{ + "JAVA", + "JETTY", // No longer supported / offered - Java SE or Tomcat (10, 9.5, 8) only + "TOMCAT", + }, false), + RequiredWith: []string{ + "site_config.0.application_stack.0.java_container_version", + }, + ConflictsWith: []string{ + "site_config.0.application_stack.0.tomcat_version", + }, + }, + + "java_container_version": { + Type: pluginsdk.TypeString, + Optional: true, + Deprecated: "This property has been deprecated in favour of `tomcat_version` and `java_embedded_server_enabled`", + RequiredWith: []string{ + "site_config.0.application_stack.0.java_container", + }, + }, + + "docker_container_name": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + AtLeastOneOf: []string{ + "site_config.0.application_stack.0.docker_container_name", + "site_config.0.application_stack.0.dotnet_version", + "site_config.0.application_stack.0.dotnet_core_version", + "site_config.0.application_stack.0.java_version", + "site_config.0.application_stack.0.node_version", + "site_config.0.application_stack.0.php_version", + "site_config.0.application_stack.0.python_version", + "site_config.0.application_stack.0.python", + }, + RequiredWith: []string{ + "site_config.0.application_stack.0.docker_container_tag", + }, + }, + + "docker_container_registry": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "docker_container_tag": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + RequiredWith: []string{ + "site_config.0.application_stack.0.docker_container_name", + }, + }, + + "current_stack": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, // This will be set to the configured type from above if not explicitly set + ValidateFunc: validation.StringInSlice([]string{ + "dotnet", + "dotnetcore", + "node", + "python", + "php", + "java", + }, false), + }, + }, + }, + } +} + +func windowsApplicationStackSchemaComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "dotnet_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "dotnet_core_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "php_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "python": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "python_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "node_version": { // Discarded by service if JavaVersion is specified + Type: pluginsdk.TypeString, + Computed: true, + }, + + "java_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "java_embedded_server_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "tomcat_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "java_container": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "java_container_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "docker_container_name": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "docker_container_registry": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "docker_container_tag": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "current_stack": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + } +} + +type ApplicationStackLinux struct { + NetFrameworkVersion string `tfschema:"dotnet_version"` + GoVersion string `tfschema:"go_version"` + PhpVersion string `tfschema:"php_version"` + PythonVersion string `tfschema:"python_version"` + NodeVersion string `tfschema:"node_version"` + JavaVersion string `tfschema:"java_version"` + JavaServer string `tfschema:"java_server"` + JavaServerVersion string `tfschema:"java_server_version"` + DockerImageTag string `tfschema:"docker_image_tag"` + DockerImage string `tfschema:"docker_image"` + RubyVersion string `tfschema:"ruby_version"` +} + +func linuxApplicationStackSchema() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "dotnet_version": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "3.1", + "5.0", // deprecated + "6.0", + "7.0", + }, false), + ExactlyOneOf: []string{ + "site_config.0.application_stack.0.docker_image", + "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.php_version", + "site_config.0.application_stack.0.python_version", + "site_config.0.application_stack.0.ruby_version", + "site_config.0.application_stack.0.go_version", + }, + }, + + "go_version": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "1.19", + "1.18", + }, false), + ExactlyOneOf: []string{ + "site_config.0.application_stack.0.docker_image", + "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.php_version", + "site_config.0.application_stack.0.python_version", + "site_config.0.application_stack.0.ruby_version", + "site_config.0.application_stack.0.go_version", + }, + }, + + "php_version": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "7.4", + "8.0", + "8.1", + }, false), + ExactlyOneOf: []string{ + "site_config.0.application_stack.0.docker_image", + "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.php_version", + "site_config.0.application_stack.0.python_version", + "site_config.0.application_stack.0.ruby_version", + "site_config.0.application_stack.0.go_version", + }, + }, + + "python_version": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "3.7", + "3.8", + "3.9", + "3.10", + }, false), + ExactlyOneOf: []string{ + "site_config.0.application_stack.0.docker_image", + "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.php_version", + "site_config.0.application_stack.0.python_version", + "site_config.0.application_stack.0.ruby_version", + "site_config.0.application_stack.0.go_version", + }, + }, + + "node_version": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "12-lts", + "14-lts", + "16-lts", + "18-lts", + }, false), + ExactlyOneOf: []string{ + "site_config.0.application_stack.0.docker_image", + "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.php_version", + "site_config.0.application_stack.0.python_version", + "site_config.0.application_stack.0.ruby_version", + "site_config.0.application_stack.0.go_version", + }, + }, + + "ruby_version": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "2.6", // Deprecated - accepted but not offered in the portal. Remove in 4.0 + "2.7", // EOL 31/03/2023 https://github.com/Azure/app-service-linux-docs/blob/master/Runtime_Support/ruby_support.md Remove Ruby support in 4.0? + }, false), + ExactlyOneOf: []string{ + "site_config.0.application_stack.0.docker_image", + "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.php_version", + "site_config.0.application_stack.0.python_version", + "site_config.0.application_stack.0.ruby_version", + "site_config.0.application_stack.0.go_version", + }, + }, + + "java_version": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "8", + "11", + "17", + }, false), + ExactlyOneOf: []string{ + "site_config.0.application_stack.0.docker_image", + "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.php_version", + "site_config.0.application_stack.0.python_version", + "site_config.0.application_stack.0.ruby_version", + "site_config.0.application_stack.0.go_version", + }, + }, + + "java_server": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "JAVA", + "TOMCAT", + "JBOSSEAP", + }, false), + RequiredWith: []string{ + "site_config.0.application_stack.0.java_version", + }, + }, + + "java_server_version": { + Type: pluginsdk.TypeString, + Optional: true, + RequiredWith: []string{ + "site_config.0.application_stack.0.java_server", + }, + }, + + "docker_image": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + ExactlyOneOf: []string{ + "site_config.0.application_stack.0.docker_image", + "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.php_version", + "site_config.0.application_stack.0.python_version", + "site_config.0.application_stack.0.ruby_version", + "site_config.0.application_stack.0.go_version", + }, + RequiredWith: []string{ + "site_config.0.application_stack.0.docker_image_tag", + }, + }, + + "docker_image_tag": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + RequiredWith: []string{ + "site_config.0.application_stack.0.docker_image", + }, + }, + }, + }, + } +} + +func linuxApplicationStackSchemaComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "dotnet_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "go_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "php_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "python_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "node_version": { // Discarded by service if JavaVersion is specified + Type: pluginsdk.TypeString, + Computed: true, + }, + + "ruby_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "java_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "java_server": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "java_server_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "docker_image": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "docker_image_tag": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + } +} diff --git a/internal/services/appservice/helpers/auto_heal.go b/internal/services/appservice/helpers/auto_heal.go new file mode 100644 index 000000000000..e9ba58e5fc64 --- /dev/null +++ b/internal/services/appservice/helpers/auto_heal.go @@ -0,0 +1,573 @@ +package helpers + +import ( + "strconv" + "strings" + + "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2021-02-01/web" // nolint: staticcheck + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/appservice/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +type AutoHealSettingWindows struct { + Triggers []AutoHealTriggerWindows `tfschema:"trigger"` + Actions []AutoHealActionWindows `tfschema:"action"` +} + +type AutoHealTriggerWindows struct { + Requests []AutoHealRequestTrigger `tfschema:"requests"` + PrivateMemoryKB int `tfschema:"private_memory_kb"` // Private should be > 102400 KB (100 MB) to 13631488 KB (13 GB), defaults to 0 however and is always present. + StatusCodes []AutoHealStatusCodeTrigger `tfschema:"status_code"` // 0 or more, ranges split by `-`, ranges cannot use sub-status or win32 code + SlowRequests []AutoHealSlowRequest `tfschema:"slow_request"` +} + +type AutoHealRequestTrigger struct { + Count int `tfschema:"count"` + Interval string `tfschema:"interval"` +} + +type AutoHealStatusCodeTrigger struct { + StatusCodeRange string `tfschema:"status_code_range"` // Conflicts with `StatusCode`, `Win32Code`, and `SubStatus` when not a single value... + SubStatus int `tfschema:"sub_status"` + Win32Status string `tfschema:"win32_status"` + Path string `tfschema:"path"` + Count int `tfschema:"count"` + Interval string `tfschema:"interval"` // Format - hh:mm:ss +} + +type AutoHealSlowRequest struct { + TimeTaken string `tfschema:"time_taken"` + Interval string `tfschema:"interval"` + Count int `tfschema:"count"` + Path string `tfschema:"path"` +} + +type AutoHealActionWindows struct { + ActionType string `tfschema:"action_type"` // Enum + CustomAction []AutoHealCustomAction `tfschema:"custom_action"` // Max: 1, needs `action_type` to be "Custom" + MinimumProcessTime string `tfschema:"minimum_process_execution_time"` // Minimum uptime for process before action will trigger +} + +type AutoHealCustomAction struct { + Executable string `tfschema:"executable"` + Parameters string `tfschema:"parameters"` +} + +func autoHealSettingSchemaWindows() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "trigger": autoHealTriggerSchemaWindows(), + + "action": autoHealActionSchemaWindows(), + }, + }, + RequiredWith: []string{ + "site_config.0.auto_heal_enabled", + }, + } +} + +func autoHealSettingSchemaWindowsComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "trigger": autoHealTriggerSchemaWindowsComputed(), + + "action": autoHealActionSchemaWindowsComputed(), + }, + }, + } +} + +func autoHealActionSchemaWindows() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Required: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "action_type": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.AutoHealActionTypeCustomAction), + string(web.AutoHealActionTypeLogEvent), + string(web.AutoHealActionTypeRecycle), + }, false), + }, + + "custom_action": { + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "executable": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "parameters": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, + }, + + "minimum_process_execution_time": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + // ValidateFunc: // TODO - Time in hh:mm:ss, because why not... + }, + }, + }, + } +} + +func autoHealActionSchemaWindowsComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "action_type": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "custom_action": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "executable": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "parameters": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + + "minimum_process_execution_time": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + } +} + +// (@jackofallops) - trigger schemas intentionally left long-hand for now +func autoHealTriggerSchemaWindows() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Required: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "requests": { + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "count": { + Type: pluginsdk.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + + "interval": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validate.AutoHealInterval, // TODO should be hh:mm:ss - This is too loose, need to improve + }, + }, + }, + }, + + "private_memory_kb": { + Type: pluginsdk.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(102400, 13631488), + }, + + "status_code": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "status_code_range": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validate.StatusCodeRange, + }, + + "count": { + Type: pluginsdk.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + + "interval": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validate.AutoHealInterval, + }, + + "sub_status": { + Type: pluginsdk.TypeInt, + Optional: true, + }, + + "win32_status": { + Type: pluginsdk.TypeString, + Optional: true, + }, + + "path": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, + }, + + "slow_request": { + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "time_taken": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validate.AutoHealInterval, + }, + + "interval": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validate.AutoHealInterval, + }, + + "count": { + Type: pluginsdk.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + + "path": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, + }, + }, + }, + } +} + +func autoHealTriggerSchemaWindowsComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "requests": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "count": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + + "interval": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + + "private_memory_kb": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + + "status_code": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "status_code_range": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "count": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + + "interval": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "sub_status": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + + "win32_status": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "path": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + + "slow_request": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "time_taken": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "interval": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "count": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + + "path": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + } +} + +func expandAutoHealSettingsWindows(autoHealSettings []AutoHealSettingWindows) *web.AutoHealRules { + if len(autoHealSettings) == 0 { + return &web.AutoHealRules{} + } + + result := &web.AutoHealRules{ + Triggers: &web.AutoHealTriggers{}, + Actions: &web.AutoHealActions{}, + } + + autoHeal := autoHealSettings[0] + + triggers := autoHeal.Triggers[0] + if len(triggers.Requests) == 1 { + result.Triggers.Requests = &web.RequestsBasedTrigger{ + Count: pointer.To(int32(triggers.Requests[0].Count)), + TimeInterval: pointer.To(triggers.Requests[0].Interval), + } + } + + if len(triggers.SlowRequests) == 1 { + result.Triggers.SlowRequests = &web.SlowRequestsBasedTrigger{ + TimeTaken: pointer.To(triggers.SlowRequests[0].TimeTaken), + TimeInterval: pointer.To(triggers.SlowRequests[0].Interval), + Count: pointer.To(int32(triggers.SlowRequests[0].Count)), + } + if triggers.SlowRequests[0].Path != "" { + result.Triggers.SlowRequests.Path = pointer.To(triggers.SlowRequests[0].Path) + } + } + + if triggers.PrivateMemoryKB != 0 { + result.Triggers.PrivateBytesInKB = pointer.To(int32(triggers.PrivateMemoryKB)) + } + + if len(triggers.StatusCodes) > 0 { + statusCodeTriggers := make([]web.StatusCodesBasedTrigger, 0) + statusCodeRangeTriggers := make([]web.StatusCodesRangeBasedTrigger, 0) + for _, s := range triggers.StatusCodes { + statusCodeTrigger := web.StatusCodesBasedTrigger{} + statusCodeRangeTrigger := web.StatusCodesRangeBasedTrigger{} + parts := strings.Split(s.StatusCodeRange, "-") + if len(parts) == 2 { + statusCodeRangeTrigger.StatusCodes = pointer.To(s.StatusCodeRange) + statusCodeRangeTrigger.Count = pointer.To(int32(s.Count)) + statusCodeRangeTrigger.TimeInterval = pointer.To(s.Interval) + if s.Path != "" { + statusCodeRangeTrigger.Path = pointer.To(s.Path) + } + statusCodeRangeTriggers = append(statusCodeRangeTriggers, statusCodeRangeTrigger) + } else { + statusCode, err := strconv.Atoi(s.StatusCodeRange) + if err == nil { + statusCodeTrigger.Status = pointer.To(int32(statusCode)) + } + statusCodeTrigger.Count = pointer.To(int32(s.Count)) + statusCodeTrigger.TimeInterval = pointer.To(s.Interval) + if s.Path != "" { + statusCodeTrigger.Path = pointer.To(s.Path) + } + statusCodeTriggers = append(statusCodeTriggers, statusCodeTrigger) + } + } + result.Triggers.StatusCodes = &statusCodeTriggers + result.Triggers.StatusCodesRange = &statusCodeRangeTriggers + } + + action := autoHeal.Actions[0] + result.Actions.ActionType = web.AutoHealActionType(action.ActionType) + result.Actions.MinProcessExecutionTime = pointer.To(action.MinimumProcessTime) + if len(action.CustomAction) != 0 { + customAction := action.CustomAction[0] + result.Actions.CustomAction = &web.AutoHealCustomAction{ + Exe: pointer.To(customAction.Executable), + Parameters: pointer.To(customAction.Parameters), + } + } + + return result +} + +func flattenAutoHealSettingsWindows(autoHealRules *web.AutoHealRules) []AutoHealSettingWindows { + if autoHealRules == nil { + return nil + } + + result := AutoHealSettingWindows{} + // Triggers + if autoHealRules.Triggers != nil { + resultTrigger := AutoHealTriggerWindows{} + triggers := *autoHealRules.Triggers + if triggers.Requests != nil { + count := 0 + if triggers.Requests.Count != nil { + count = int(*triggers.Requests.Count) + } + resultTrigger.Requests = []AutoHealRequestTrigger{{ + Count: count, + Interval: pointer.From(triggers.Requests.TimeInterval), + }} + } + + if privateBytes := triggers.PrivateBytesInKB; privateBytes != nil && *privateBytes != 0 { + resultTrigger.PrivateMemoryKB = int(*triggers.PrivateBytesInKB) + } + + statusCodeTriggers := make([]AutoHealStatusCodeTrigger, 0) + if triggers.StatusCodes != nil { + for _, s := range *triggers.StatusCodes { + t := AutoHealStatusCodeTrigger{ + Interval: pointer.From(s.TimeInterval), + Path: pointer.From(s.Path), + } + + if s.Status != nil { + t.StatusCodeRange = strconv.Itoa(int(*s.Status)) + } + + if s.Count != nil { + t.Count = int(*s.Count) + } + + if s.SubStatus != nil { + t.SubStatus = int(*s.SubStatus) + } + statusCodeTriggers = append(statusCodeTriggers, t) + } + } + if triggers.StatusCodesRange != nil { + for _, s := range *triggers.StatusCodesRange { + t := AutoHealStatusCodeTrigger{ + Interval: pointer.From(s.TimeInterval), + Path: pointer.From(s.Path), + } + if s.Count != nil { + t.Count = int(*s.Count) + } + + if s.StatusCodes != nil { + t.StatusCodeRange = *s.StatusCodes + } + statusCodeTriggers = append(statusCodeTriggers, t) + } + } + resultTrigger.StatusCodes = statusCodeTriggers + + slowRequestTriggers := make([]AutoHealSlowRequest, 0) + if triggers.SlowRequests != nil { + slowRequestTriggers = append(slowRequestTriggers, AutoHealSlowRequest{ + TimeTaken: pointer.From(triggers.SlowRequests.TimeTaken), + Interval: pointer.From(triggers.SlowRequests.TimeInterval), + Count: int(pointer.From(triggers.SlowRequests.Count)), + Path: pointer.From(triggers.SlowRequests.Path), + }) + } + resultTrigger.SlowRequests = slowRequestTriggers + result.Triggers = []AutoHealTriggerWindows{resultTrigger} + } + + // Actions + if autoHealRules.Actions != nil { + actions := *autoHealRules.Actions + customActions := make([]AutoHealCustomAction, 0) + if actions.CustomAction != nil { + customActions = append(customActions, AutoHealCustomAction{ + Executable: pointer.From(actions.CustomAction.Exe), + Parameters: pointer.From(actions.CustomAction.Parameters), + }) + } + + resultActions := AutoHealActionWindows{ + ActionType: string(actions.ActionType), + CustomAction: customActions, + MinimumProcessTime: pointer.From(actions.MinProcessExecutionTime), + } + result.Actions = []AutoHealActionWindows{resultActions} + } + + if result.Actions != nil || result.Triggers != nil { + return []AutoHealSettingWindows{result} + } + + return nil +} diff --git a/internal/services/appservice/helpers/common_web_app_schema.go b/internal/services/appservice/helpers/common_web_app_schema.go new file mode 100644 index 000000000000..f9768de89dcc --- /dev/null +++ b/internal/services/appservice/helpers/common_web_app_schema.go @@ -0,0 +1,1364 @@ +package helpers + +import ( + "fmt" + "strconv" + "time" + + "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2021-02-01/web" // nolint: staticcheck + "github.com/Azure/go-autorest/autorest/date" + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +type VirtualApplication struct { + VirtualPath string `tfschema:"virtual_path"` + PhysicalPath string `tfschema:"physical_path"` + Preload bool `tfschema:"preload"` + VirtualDirectories []VirtualDirectory `tfschema:"virtual_directory"` +} + +type VirtualDirectory struct { + VirtualPath string `tfschema:"virtual_path"` + PhysicalPath string `tfschema:"physical_path"` +} + +func virtualApplicationsSchema() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "virtual_path": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "physical_path": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "preload": { + Type: pluginsdk.TypeBool, + Required: true, + }, + + "virtual_directory": { + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "virtual_path": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "physical_path": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, + }, + }, + }, + } +} + +func virtualApplicationsSchemaComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "virtual_path": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "physical_path": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "preload": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "virtual_directory": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "virtual_path": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "physical_path": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + } +} + +type StorageAccount struct { + Name string `tfschema:"name"` + Type string `tfschema:"type"` + AccountName string `tfschema:"account_name"` + ShareName string `tfschema:"share_name"` + AccessKey string `tfschema:"access_key"` + MountPath string `tfschema:"mount_path"` +} + +func StorageAccountSchema() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "type": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.AzureStorageTypeAzureBlob), + string(web.AzureStorageTypeAzureFiles), + }, false), + }, + + "account_name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "share_name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "access_key": { + Type: pluginsdk.TypeString, + Required: true, + Sensitive: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "mount_path": { + Type: pluginsdk.TypeString, + Optional: true, + }, + }, + }, + } +} + +func StorageAccountSchemaWindows() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "type": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.AzureStorageTypeAzureFiles), + }, false), + }, + + "account_name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "share_name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "access_key": { + Type: pluginsdk.TypeString, + Required: true, + Sensitive: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "mount_path": { + Type: pluginsdk.TypeString, + Optional: true, + }, + }, + }, + } +} + +func StorageAccountSchemaComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "type": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "account_name": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "share_name": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "access_key": { + Type: pluginsdk.TypeString, + Computed: true, + Sensitive: true, + }, + + "mount_path": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + } +} + +type Backup struct { + Name string `tfschema:"name"` + StorageAccountUrl string `tfschema:"storage_account_url"` + Enabled bool `tfschema:"enabled"` + Schedule []BackupSchedule `tfschema:"schedule"` +} + +type BackupSchedule struct { + FrequencyInterval int `tfschema:"frequency_interval"` + FrequencyUnit string `tfschema:"frequency_unit"` + KeepAtLeastOneBackup bool `tfschema:"keep_at_least_one_backup"` + RetentionPeriodDays int `tfschema:"retention_period_days"` + StartTime string `tfschema:"start_time"` + LastExecutionTime string `tfschema:"last_execution_time"` +} + +func BackupSchema() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + Description: "The name which should be used for this Backup.", + }, + + "storage_account_url": { + Type: pluginsdk.TypeString, + Required: true, + Sensitive: true, + ValidateFunc: validation.IsURLWithHTTPS, + Description: "The SAS URL to the container.", + }, + + "enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + Description: "Should this backup job be enabled?", + }, + + "schedule": { + Type: pluginsdk.TypeList, + Required: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "frequency_interval": { + Type: pluginsdk.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(0, 1000), + Description: "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`).", + }, + + "frequency_unit": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "Day", + "Hour", + }, false), + Description: "The unit of time for how often the backup should take place. Possible values include: `Day` and `Hour`.", + }, + + "keep_at_least_one_backup": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + Description: "Should the service keep at least one backup, regardless of age of backup. Defaults to `false`.", + }, + + "retention_period_days": { + Type: pluginsdk.TypeInt, + Optional: true, + Default: 30, + ValidateFunc: validation.IntBetween(0, 9999999), + Description: "After how many days backups should be deleted.", + }, + + "start_time": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + Description: "When the schedule should start working in RFC-3339 format.", + ValidateFunc: validation.IsRFC3339Time, + }, + + "last_execution_time": { + Type: pluginsdk.TypeString, + Computed: true, + Description: "The time the backup was last attempted.", + }, + }, + }, + }, + }, + }, + } +} + +func BackupSchemaComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Computed: true, + Description: "The name of this Backup.", + }, + + "storage_account_url": { + Type: pluginsdk.TypeString, + Computed: true, + Sensitive: true, + Description: "The SAS URL to the container.", + }, + + "enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + Description: "Is this backup job enabled?", + }, + + "schedule": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "frequency_interval": { + Type: pluginsdk.TypeInt, + Computed: true, + Description: "How often the backup should is executed in multiples of the `frequency_unit`.", + }, + + "frequency_unit": { + Type: pluginsdk.TypeString, + Computed: true, + Description: "The unit of time for how often the backup takes place.", + }, + + "keep_at_least_one_backup": { + Type: pluginsdk.TypeBool, + Computed: true, + Description: "Does the service keep at least one backup, regardless of age of backup.", + }, + + "retention_period_days": { + Type: pluginsdk.TypeInt, + Computed: true, + Description: "After how many days are backups deleted.", + }, + + "start_time": { + Type: pluginsdk.TypeString, + Computed: true, + Description: "When the schedule should start working in RFC-3339 format.", + }, + + "last_execution_time": { + Type: pluginsdk.TypeString, + Computed: true, + Description: "The time the backup was last attempted.", + }, + }, + }, + }, + }, + }, + } +} + +type ConnectionString struct { + Name string `tfschema:"name"` + Type string `tfschema:"type"` + Value string `tfschema:"value"` +} + +func ConnectionStringSchema() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + Description: "The name which should be used for this Connection.", + }, + + "type": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.ConnectionStringTypeAPIHub), + string(web.ConnectionStringTypeCustom), + string(web.ConnectionStringTypeDocDb), + string(web.ConnectionStringTypeEventHub), + string(web.ConnectionStringTypeMySQL), + string(web.ConnectionStringTypeNotificationHub), + string(web.ConnectionStringTypePostgreSQL), + string(web.ConnectionStringTypeRedisCache), + string(web.ConnectionStringTypeServiceBus), + string(web.ConnectionStringTypeSQLAzure), + string(web.ConnectionStringTypeSQLServer), + }, false), + Description: "Type of database. Possible values include: `MySQL`, `SQLServer`, `SQLAzure`, `Custom`, `NotificationHub`, `ServiceBus`, `EventHub`, `APIHub`, `DocDb`, `RedisCache`, and `PostgreSQL`.", + }, + + "value": { + Type: pluginsdk.TypeString, + Required: true, + Sensitive: true, + Description: "The connection string value.", + }, + }, + }, + } +} + +func ConnectionStringSchemaComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeSet, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Computed: true, + Description: "The name of this Connection.", + }, + + "type": { + Type: pluginsdk.TypeString, + Computed: true, + Description: "The type of database.", + }, + + "value": { + Type: pluginsdk.TypeString, + Computed: true, + Sensitive: true, + Description: "The connection string value.", + }, + }, + }, + } +} + +type LogsConfig struct { + ApplicationLogs []ApplicationLog `tfschema:"application_logs"` + HttpLogs []HttpLog `tfschema:"http_logs"` + DetailedErrorMessages bool `tfschema:"detailed_error_messages"` + FailedRequestTracing bool `tfschema:"failed_request_tracing"` +} + +type ApplicationLog struct { + FileSystemLevel string `tfschema:"file_system_level"` + AzureBlobStorage []AzureBlobStorage `tfschema:"azure_blob_storage"` +} + +type AzureBlobStorage struct { + Level string `tfschema:"level"` + SasUrl string `tfschema:"sas_url"` + RetentionInDays int `tfschema:"retention_in_days"` +} + +type HttpLog struct { + FileSystems []LogsFileSystem `tfschema:"file_system"` + AzureBlobStorage []AzureBlobStorageHttp `tfschema:"azure_blob_storage"` +} + +type AzureBlobStorageHttp struct { + SasUrl string `tfschema:"sas_url"` + RetentionInDays int `tfschema:"retention_in_days"` +} + +type LogsFileSystem struct { + RetentionMB int `tfschema:"retention_in_mb"` + RetentionDays int `tfschema:"retention_in_days"` +} + +func LogsConfigSchema() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "application_logs": applicationLogSchema(), + + "http_logs": httpLogSchema(), + + "failed_request_tracing": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "detailed_error_messages": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + }, + }, + } +} + +func LogsConfigSchemaComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "application_logs": applicationLogSchemaComputed(), + + "http_logs": httpLogSchemaComputed(), + + "failed_request_tracing": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "detailed_error_messages": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + }, + }, + } +} + +func applicationLogSchema() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "file_system_level": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.LogLevelError), + string(web.LogLevelInformation), + string(web.LogLevelVerbose), + string(web.LogLevelWarning), + }, false), + }, + + "azure_blob_storage": appLogBlobStorageSchema(), + }, + }, + } +} + +func applicationLogSchemaComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "file_system_level": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "azure_blob_storage": appLogBlobStorageSchemaComputed(), + }, + }, + } +} + +func appLogBlobStorageSchema() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "level": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.LogLevelError), + string(web.LogLevelInformation), + string(web.LogLevelVerbose), + string(web.LogLevelWarning), + }, false), + }, + "sas_url": { + Type: pluginsdk.TypeString, + Required: true, + }, + "retention_in_days": { + Type: pluginsdk.TypeInt, + Required: true, + // TODO: Validation here? + }, + }, + }, + } +} + +func appLogBlobStorageSchemaComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "level": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "sas_url": { + Type: pluginsdk.TypeString, + Computed: true, + Sensitive: true, + }, + "retention_in_days": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + }, + }, + } +} + +func httpLogSchema() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "file_system": httpLogFileSystemSchema(), + + "azure_blob_storage": httpLogBlobStorageSchema(), + }, + }, + } +} + +func httpLogSchemaComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "file_system": httpLogFileSystemSchemaComputed(), + + "azure_blob_storage": httpLogBlobStorageSchemaComputed(), + }, + }, + } +} + +func httpLogFileSystemSchema() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + ConflictsWith: []string{"logs.0.http_logs.0.azure_blob_storage"}, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "retention_in_mb": { + Type: pluginsdk.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(25, 100), + }, + + "retention_in_days": { + Type: pluginsdk.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(0), + }, + }, + }, + } +} + +func httpLogFileSystemSchemaComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "retention_in_mb": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + + "retention_in_days": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + }, + }, + } +} + +func httpLogBlobStorageSchema() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + ConflictsWith: []string{"logs.0.http_logs.0.file_system"}, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "sas_url": { + Type: pluginsdk.TypeString, + Required: true, + Sensitive: true, + }, + "retention_in_days": { + Type: pluginsdk.TypeInt, + Optional: true, + Default: 0, + ValidateFunc: validation.IntAtLeast(0), // Variable validation here based on the Service Plan SKU + }, + }, + }, + } +} + +func httpLogBlobStorageSchemaComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "sas_url": { + Type: pluginsdk.TypeString, + Computed: true, + Sensitive: true, + }, + "retention_in_days": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + }, + }, + } +} + +func ExpandLogsConfig(config []LogsConfig) *web.SiteLogsConfig { + result := &web.SiteLogsConfig{} + if len(config) == 0 { + return result + } + + result.SiteLogsConfigProperties = &web.SiteLogsConfigProperties{} + + logsConfig := config[0] + + if len(logsConfig.ApplicationLogs) == 1 { + appLogs := logsConfig.ApplicationLogs[0] + result.SiteLogsConfigProperties.ApplicationLogs = &web.ApplicationLogsConfig{ + FileSystem: &web.FileSystemApplicationLogsConfig{ + Level: web.LogLevel(appLogs.FileSystemLevel), + }, + } + if len(appLogs.AzureBlobStorage) == 1 { + appLogsBlobs := appLogs.AzureBlobStorage[0] + result.SiteLogsConfigProperties.ApplicationLogs.AzureBlobStorage = &web.AzureBlobStorageApplicationLogsConfig{ + Level: web.LogLevel(appLogsBlobs.Level), + SasURL: pointer.To(appLogsBlobs.SasUrl), + RetentionInDays: pointer.To(int32(appLogsBlobs.RetentionInDays)), + } + } + } + + if len(logsConfig.HttpLogs) == 1 { + httpLogs := logsConfig.HttpLogs[0] + result.HTTPLogs = &web.HTTPLogsConfig{} + + if len(httpLogs.FileSystems) == 1 { + httpLogFileSystem := httpLogs.FileSystems[0] + result.HTTPLogs.FileSystem = &web.FileSystemHTTPLogsConfig{ + Enabled: pointer.To(true), + RetentionInMb: pointer.To(int32(httpLogFileSystem.RetentionMB)), + RetentionInDays: pointer.To(int32(httpLogFileSystem.RetentionDays)), + } + } + + if len(httpLogs.AzureBlobStorage) == 1 { + httpLogsBlobStorage := httpLogs.AzureBlobStorage[0] + result.HTTPLogs.AzureBlobStorage = &web.AzureBlobStorageHTTPLogsConfig{ + Enabled: pointer.To(httpLogsBlobStorage.SasUrl != ""), + SasURL: pointer.To(httpLogsBlobStorage.SasUrl), + RetentionInDays: pointer.To(int32(httpLogsBlobStorage.RetentionInDays)), + } + } + } + + result.DetailedErrorMessages = &web.EnabledConfig{ + Enabled: pointer.To(logsConfig.DetailedErrorMessages), + } + + result.FailedRequestsTracing = &web.EnabledConfig{ + Enabled: pointer.To(logsConfig.FailedRequestTracing), + } + + return result +} + +func ExpandBackupConfig(backupConfigs []Backup) (*web.BackupRequest, error) { + result := &web.BackupRequest{} + if len(backupConfigs) == 0 { + return result, nil + } + + backupConfig := backupConfigs[0] + backupSchedule := backupConfig.Schedule[0] + result.BackupRequestProperties = &web.BackupRequestProperties{ + Enabled: pointer.To(backupConfig.Enabled), + BackupName: pointer.To(backupConfig.Name), + StorageAccountURL: pointer.To(backupConfig.StorageAccountUrl), + BackupSchedule: &web.BackupSchedule{ + FrequencyInterval: pointer.To(int32(backupSchedule.FrequencyInterval)), + FrequencyUnit: web.FrequencyUnit(backupSchedule.FrequencyUnit), + KeepAtLeastOneBackup: pointer.To(backupSchedule.KeepAtLeastOneBackup), + RetentionPeriodInDays: pointer.To(int32(backupSchedule.RetentionPeriodDays)), + }, + } + + if backupSchedule.StartTime != "" { + dateTimeToStart, err := time.Parse(time.RFC3339, backupSchedule.StartTime) + if err != nil { + return nil, fmt.Errorf("parsing back up start_time: %+v", err) + } + result.BackupRequestProperties.BackupSchedule.StartTime = &date.Time{Time: dateTimeToStart} + } + + return result, nil +} + +func ExpandStorageConfig(storageConfigs []StorageAccount) *web.AzureStoragePropertyDictionaryResource { + storageAccounts := make(map[string]*web.AzureStorageInfoValue) + result := &web.AzureStoragePropertyDictionaryResource{} + if len(storageConfigs) == 0 { + result.Properties = storageAccounts + return result + } + + for _, v := range storageConfigs { + storageAccounts[v.Name] = &web.AzureStorageInfoValue{ + Type: web.AzureStorageType(v.Type), + AccountName: pointer.To(v.AccountName), + ShareName: pointer.To(v.ShareName), + AccessKey: pointer.To(v.AccessKey), + MountPath: pointer.To(v.MountPath), + } + } + + result.Properties = storageAccounts + + return result +} + +func ExpandConnectionStrings(connectionStringsConfig []ConnectionString) *web.ConnectionStringDictionary { + result := &web.ConnectionStringDictionary{} + if len(connectionStringsConfig) == 0 { + return result + } + + connectionStrings := make(map[string]*web.ConnStringValueTypePair) + for _, v := range connectionStringsConfig { + connectionStrings[v.Name] = &web.ConnStringValueTypePair{ + Value: pointer.To(v.Value), + Type: web.ConnectionStringType(v.Type), + } + } + result.Properties = connectionStrings + + return result +} + +func expandVirtualApplications(virtualApplicationConfig []VirtualApplication) *[]web.VirtualApplication { + if len(virtualApplicationConfig) == 0 { + return nil + } + + result := make([]web.VirtualApplication, 0) + + for _, v := range virtualApplicationConfig { + virtualApp := web.VirtualApplication{ + VirtualPath: pointer.To(v.VirtualPath), + PhysicalPath: pointer.To(v.PhysicalPath), + PreloadEnabled: pointer.To(v.Preload), + } + if len(v.VirtualDirectories) > 0 { + virtualDirs := make([]web.VirtualDirectory, 0) + for _, d := range v.VirtualDirectories { + virtualDirs = append(virtualDirs, web.VirtualDirectory{ + VirtualPath: pointer.To(d.VirtualPath), + PhysicalPath: pointer.To(d.PhysicalPath), + }) + } + virtualApp.VirtualDirectories = &virtualDirs + } + + result = append(result, virtualApp) + } + return &result +} + +func expandVirtualApplicationsForUpdate(virtualApplicationConfig []VirtualApplication) *[]web.VirtualApplication { + if len(virtualApplicationConfig) == 0 { + // to remove this block from the config we need to give the service the original default back, sending an empty struct leaves the previous config in place + return &[]web.VirtualApplication{ + { + VirtualPath: pointer.To("/"), + PhysicalPath: pointer.To("site\\wwwroot"), + PreloadEnabled: pointer.To(true), + }, + } + } + + result := make([]web.VirtualApplication, 0) + + for _, v := range virtualApplicationConfig { + virtualApp := web.VirtualApplication{ + VirtualPath: pointer.To(v.VirtualPath), + PhysicalPath: pointer.To(v.PhysicalPath), + PreloadEnabled: pointer.To(v.Preload), + } + if len(v.VirtualDirectories) > 0 { + virtualDirs := make([]web.VirtualDirectory, 0) + for _, d := range v.VirtualDirectories { + virtualDirs = append(virtualDirs, web.VirtualDirectory{ + VirtualPath: pointer.To(d.VirtualPath), + PhysicalPath: pointer.To(d.PhysicalPath), + }) + } + virtualApp.VirtualDirectories = &virtualDirs + } + + result = append(result, virtualApp) + } + return &result +} + +func FlattenBackupConfig(backupRequest web.BackupRequest) []Backup { + if backupRequest.BackupRequestProperties == nil { + return []Backup{} + } + props := *backupRequest.BackupRequestProperties + backup := Backup{} + if props.BackupName != nil { + backup.Name = *props.BackupName + } + + if props.StorageAccountURL != nil { + backup.StorageAccountUrl = *props.StorageAccountURL + } + + if props.Enabled != nil { + backup.Enabled = *props.Enabled + } + + if schedule := props.BackupSchedule; schedule != nil { + backupSchedule := BackupSchedule{ + FrequencyUnit: string(schedule.FrequencyUnit), + } + if schedule.FrequencyInterval != nil { + backupSchedule.FrequencyInterval = int(*schedule.FrequencyInterval) + } + + if schedule.KeepAtLeastOneBackup != nil { + backupSchedule.KeepAtLeastOneBackup = *schedule.KeepAtLeastOneBackup + } + + if schedule.RetentionPeriodInDays != nil { + backupSchedule.RetentionPeriodDays = int(*schedule.RetentionPeriodInDays) + } + + if schedule.StartTime != nil && !schedule.StartTime.IsZero() { + backupSchedule.StartTime = schedule.StartTime.Format(time.RFC3339) + } + + if schedule.LastExecutionTime != nil && !schedule.LastExecutionTime.IsZero() { + backupSchedule.LastExecutionTime = schedule.LastExecutionTime.Format(time.RFC3339) + } + + backup.Schedule = []BackupSchedule{backupSchedule} + } + + return []Backup{backup} +} + +func FlattenLogsConfig(logsConfig web.SiteLogsConfig) []LogsConfig { + if logsConfig.SiteLogsConfigProperties == nil { + return []LogsConfig{} + } + props := *logsConfig.SiteLogsConfigProperties + if onlyDefaultLoggingConfig(props) { + return nil + } + + logs := LogsConfig{} + + if props.ApplicationLogs != nil { + appLogs := *props.ApplicationLogs + applicationLog := ApplicationLog{} + + if appLogs.FileSystem != nil && appLogs.FileSystem.Level != web.LogLevelOff { + applicationLog.FileSystemLevel = string(appLogs.FileSystem.Level) + if appLogs.AzureBlobStorage != nil && appLogs.AzureBlobStorage.Level != web.LogLevelOff { + blobStorage := AzureBlobStorage{ + Level: string(appLogs.AzureBlobStorage.Level), + } + + blobStorage.SasUrl = pointer.From(appLogs.AzureBlobStorage.SasURL) + + blobStorage.RetentionInDays = int(pointer.From(appLogs.AzureBlobStorage.RetentionInDays)) + + applicationLog.AzureBlobStorage = []AzureBlobStorage{blobStorage} + } + logs.ApplicationLogs = []ApplicationLog{applicationLog} + } + } + + if props.HTTPLogs != nil { + httpLogs := *props.HTTPLogs + httpLog := HttpLog{} + + if httpLogs.FileSystem != nil && (httpLogs.FileSystem.Enabled != nil && *httpLogs.FileSystem.Enabled) { + fileSystem := LogsFileSystem{} + if httpLogs.FileSystem.RetentionInMb != nil { + fileSystem.RetentionMB = int(*httpLogs.FileSystem.RetentionInMb) + } + + if httpLogs.FileSystem.RetentionInDays != nil { + fileSystem.RetentionDays = int(*httpLogs.FileSystem.RetentionInDays) + } + + httpLog.FileSystems = []LogsFileSystem{fileSystem} + } + + if httpLogs.AzureBlobStorage != nil && (httpLogs.AzureBlobStorage.Enabled != nil && *httpLogs.AzureBlobStorage.Enabled) { + blobStorage := AzureBlobStorageHttp{} + if httpLogs.AzureBlobStorage.SasURL != nil { + blobStorage.SasUrl = *httpLogs.AzureBlobStorage.SasURL + } + + if httpLogs.AzureBlobStorage.RetentionInDays != nil { + blobStorage.RetentionInDays = int(*httpLogs.AzureBlobStorage.RetentionInDays) + } + + if blobStorage.RetentionInDays != 0 || blobStorage.SasUrl != "" { + httpLog.AzureBlobStorage = []AzureBlobStorageHttp{blobStorage} + } + } + + if httpLog.FileSystems != nil || httpLog.AzureBlobStorage != nil { + logs.HttpLogs = []HttpLog{httpLog} + } + } + + // logs.DetailedErrorMessages = false + if props.DetailedErrorMessages != nil && props.DetailedErrorMessages.Enabled != nil { + logs.DetailedErrorMessages = *props.DetailedErrorMessages.Enabled + } + + // logs.FailedRequestTracing = false + if props.FailedRequestsTracing != nil && props.FailedRequestsTracing.Enabled != nil { + logs.FailedRequestTracing = *props.FailedRequestsTracing.Enabled + } + + return []LogsConfig{logs} +} + +func onlyDefaultLoggingConfig(props web.SiteLogsConfigProperties) bool { + if props.ApplicationLogs == nil || props.HTTPLogs == nil || props.FailedRequestsTracing == nil || props.DetailedErrorMessages == nil { + return false + } + if props.ApplicationLogs.FileSystem != nil && props.ApplicationLogs.FileSystem.Level != web.LogLevelOff { + return false + } + if props.ApplicationLogs.AzureBlobStorage != nil && props.ApplicationLogs.AzureBlobStorage.Level != web.LogLevelOff { + return false + } + if props.HTTPLogs.FileSystem != nil && props.HTTPLogs.FileSystem.Enabled != nil && (*props.HTTPLogs.FileSystem.Enabled) { + return false + } + if props.HTTPLogs.AzureBlobStorage != nil && props.HTTPLogs.AzureBlobStorage.Enabled != nil && (*props.HTTPLogs.AzureBlobStorage.Enabled) { + return false + } + if props.FailedRequestsTracing.Enabled == nil || *props.FailedRequestsTracing.Enabled { + return false + } + if props.DetailedErrorMessages.Enabled == nil || *props.DetailedErrorMessages.Enabled { + return false + } + return true +} + +func FlattenStorageAccounts(appStorageAccounts web.AzureStoragePropertyDictionaryResource) []StorageAccount { + if len(appStorageAccounts.Properties) == 0 { + return []StorageAccount{} + } + var storageAccounts []StorageAccount + for k, v := range appStorageAccounts.Properties { + storageAccount := StorageAccount{ + Name: k, + Type: string(v.Type), + } + if v.AccountName != nil { + storageAccount.AccountName = *v.AccountName + } + + if v.ShareName != nil { + storageAccount.ShareName = *v.ShareName + } + + if v.AccessKey != nil { + storageAccount.AccessKey = *v.AccessKey + } + + if v.MountPath != nil { + storageAccount.MountPath = *v.MountPath + } + + storageAccounts = append(storageAccounts, storageAccount) + } + + return storageAccounts +} + +func FlattenConnectionStrings(appConnectionStrings web.ConnectionStringDictionary) []ConnectionString { + if len(appConnectionStrings.Properties) == 0 { + return []ConnectionString{} + } + var connectionStrings []ConnectionString + for k, v := range appConnectionStrings.Properties { + connectionString := ConnectionString{ + Name: k, + Type: string(v.Type), + } + if v.Value != nil { + connectionString.Value = *v.Value + } + connectionStrings = append(connectionStrings, connectionString) + } + + return connectionStrings +} + +func ExpandAppSettingsForUpdate(settings map[string]string) *web.StringDictionary { + appSettings := make(map[string]*string) + for k, v := range settings { + appSettings[k] = pointer.To(v) + } + + return &web.StringDictionary{ + Properties: appSettings, + } +} + +func ExpandAppSettingsForCreate(settings map[string]string) *[]web.NameValuePair { + if len(settings) > 0 { + result := make([]web.NameValuePair, 0) + for k, v := range settings { + result = append(result, web.NameValuePair{ + Name: pointer.To(k), + Value: pointer.To(v), + }) + } + return &result + } + return nil +} + +func FlattenAppSettings(input web.StringDictionary) (map[string]string, *int, error) { + maxPingFailures := "WEBSITE_HEALTHCHECK_MAXPINGFAILURES" + unmanagedSettings := []string{ + "DIAGNOSTICS_AZUREBLOBCONTAINERSASURL", + "DIAGNOSTICS_AZUREBLOBRETENTIONINDAYS", + "WEBSITE_HTTPLOGGING_CONTAINER_URL", + "WEBSITE_HTTPLOGGING_RETENTION_DAYS", + "WEBSITE_VNET_ROUTE_ALL", + "spring.datasource.password", + "spring.datasource.url", + "spring.datasource.username", + maxPingFailures, + } + + var healthCheckCount *int + appSettings := FlattenWebStringDictionary(input) + if v, ok := appSettings[maxPingFailures]; ok { + h, err := strconv.Atoi(v) + if err != nil { + return nil, nil, fmt.Errorf("could not convert max ping failures to int") + } + healthCheckCount = &h + } + + // Remove the settings the service adds for legacy reasons. + for _, v := range unmanagedSettings { //nolint:typecheck + delete(appSettings, v) + } + + return appSettings, healthCheckCount, nil +} + +func flattenVirtualApplications(appVirtualApplications *[]web.VirtualApplication) []VirtualApplication { + if appVirtualApplications == nil || onlyDefaultVirtualApplication(*appVirtualApplications) { + return nil + } + + var virtualApplications []VirtualApplication + for _, v := range *appVirtualApplications { + virtualApp := VirtualApplication{ + VirtualPath: pointer.From(v.VirtualPath), + PhysicalPath: pointer.From(v.PhysicalPath), + } + if preload := v.PreloadEnabled; preload != nil { + virtualApp.Preload = *preload + } + if v.VirtualDirectories != nil && len(*v.VirtualDirectories) > 0 { + virtualDirs := make([]VirtualDirectory, 0) + for _, d := range *v.VirtualDirectories { + virtualDir := VirtualDirectory{ + VirtualPath: pointer.From(d.VirtualPath), + PhysicalPath: pointer.From(d.PhysicalPath), + } + virtualDirs = append(virtualDirs, virtualDir) + } + virtualApp.VirtualDirectories = virtualDirs + } + virtualApplications = append(virtualApplications, virtualApp) + } + + return virtualApplications +} + +func onlyDefaultVirtualApplication(input []web.VirtualApplication) bool { + if len(input) > 1 { + return false + } + app := input[0] + if app.VirtualPath == nil || app.PhysicalPath == nil { + return false + } + if *app.VirtualPath == "/" && *app.PhysicalPath == "site\\wwwroot" && *app.PreloadEnabled && app.VirtualDirectories == nil { + return true + } + return false +} + +func DisabledLogsConfig() *web.SiteLogsConfig { + return &web.SiteLogsConfig{ + SiteLogsConfigProperties: &web.SiteLogsConfigProperties{ + DetailedErrorMessages: &web.EnabledConfig{ + Enabled: pointer.To(false), + }, + FailedRequestsTracing: &web.EnabledConfig{ + Enabled: pointer.To(false), + }, + ApplicationLogs: &web.ApplicationLogsConfig{ + FileSystem: &web.FileSystemApplicationLogsConfig{ + Level: web.LogLevelOff, + }, + AzureBlobStorage: &web.AzureBlobStorageApplicationLogsConfig{ + Level: web.LogLevelOff, + }, + }, + HTTPLogs: &web.HTTPLogsConfig{ + FileSystem: &web.FileSystemHTTPLogsConfig{ + Enabled: pointer.To(false), + }, + AzureBlobStorage: &web.AzureBlobStorageHTTPLogsConfig{ + Enabled: pointer.To(false), + }, + }, + }, + } +} + +func isFreeOrSharedServicePlan(inputSKU string) bool { + result := false + for _, sku := range freeSkus { + if inputSKU == sku { + result = true + } + } + for _, sku := range sharedSkus { + if inputSKU == sku { + result = true + } + } + return result +} diff --git a/internal/services/appservice/helpers/function_app_schema.go b/internal/services/appservice/helpers/function_app_schema.go index 072278a04611..bb2839f57bef 100644 --- a/internal/services/appservice/helpers/function_app_schema.go +++ b/internal/services/appservice/helpers/function_app_schema.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2021-02-01/web" // nolint: staticcheck + "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" apimValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/apimanagement/validate" @@ -1047,6 +1048,7 @@ func linuxFunctionAppStackSchema() *pluginsdk.Schema { Type: pluginsdk.TypeString, Optional: true, ValidateFunc: validation.StringInSlice([]string{ + "3.10", "3.9", "3.8", "3.7", @@ -1067,7 +1069,7 @@ func linuxFunctionAppStackSchema() *pluginsdk.Schema { Type: pluginsdk.TypeString, Optional: true, ValidateFunc: validation.StringInSlice([]string{ - "12", + "12", // Deprecated, and removed from portal, but seemingly accepted by API "14", "16", "18", // preview LTS Support @@ -1088,8 +1090,8 @@ func linuxFunctionAppStackSchema() *pluginsdk.Schema { Type: pluginsdk.TypeString, Optional: true, ValidateFunc: validation.StringInSlice([]string{ - "7", - "7.2", // preview LTS Support + "7", // Deprecated / not available in the portal + "7.2", }, false), ExactlyOneOf: []string{ "site_config.0.application_stack.0.dotnet_version", @@ -1120,7 +1122,7 @@ func linuxFunctionAppStackSchema() *pluginsdk.Schema { "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 Java to use. Possible values are `8`, `11`, and `17`", }, "docker": { @@ -1277,16 +1279,19 @@ func windowsFunctionAppStackSchema() *pluginsdk.Schema { return &pluginsdk.Schema{ Type: pluginsdk.TypeList, Optional: true, + Computed: true, MaxItems: 1, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ "dotnet_version": { Type: pluginsdk.TypeString, Optional: true, + Default: "v4.0", ValidateFunc: validation.StringInSlice([]string{ - "3.1", - "6", - "7", + "v3.0", + "v4.0", + "v6.0", + "v7.0", }, false), ExactlyOneOf: []string{ "site_config.0.application_stack.0.dotnet_version", @@ -1295,13 +1300,13 @@ func windowsFunctionAppStackSchema() *pluginsdk.Schema { "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` `6` and `7`", + Description: "The version of .Net. Possible values are `v3.0`, `v4.0`, `v6.0` and `v7.0`", }, "use_dotnet_isolated_runtime": { Type: pluginsdk.TypeBool, Optional: true, - Default: false, + Computed: true, ConflictsWith: []string{ "site_config.0.application_stack.0.java_version", "site_config.0.application_stack.0.node_version", @@ -1334,7 +1339,7 @@ func windowsFunctionAppStackSchema() *pluginsdk.Schema { Type: pluginsdk.TypeString, Optional: true, ValidateFunc: validation.StringInSlice([]string{ - "8", + "1.8", "11", "17", }, false), @@ -1345,7 +1350,7 @@ func windowsFunctionAppStackSchema() *pluginsdk.Schema { "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`", + Description: "The version of Java to use. Possible values are `1.8`, `11` and `17`", }, "powershell_core_version": { @@ -1368,6 +1373,7 @@ func windowsFunctionAppStackSchema() *pluginsdk.Schema { "use_custom_runtime": { Type: pluginsdk.TypeBool, Optional: true, + Computed: true, ExactlyOneOf: []string{ "site_config.0.application_stack.0.dotnet_version", "site_config.0.application_stack.0.java_version", @@ -1564,17 +1570,17 @@ func ExpandSiteConfigLinuxFunctionApp(siteConfig []SiteConfigLinuxFunctionApp, e if linuxAppStack.PythonVersion != "" { appSettings = updateOrAppendAppSettings(appSettings, "FUNCTIONS_WORKER_RUNTIME", "python", false) - expanded.LinuxFxVersion = utils.String(fmt.Sprintf("Python|%s", linuxAppStack.PythonVersion)) + expanded.LinuxFxVersion = utils.String(fmt.Sprintf("PYTHON|%s", linuxAppStack.PythonVersion)) } if linuxAppStack.JavaVersion != "" { appSettings = updateOrAppendAppSettings(appSettings, "FUNCTIONS_WORKER_RUNTIME", "java", false) - expanded.LinuxFxVersion = utils.String(fmt.Sprintf("Java|%s", linuxAppStack.JavaVersion)) + expanded.LinuxFxVersion = utils.String(fmt.Sprintf("JAVA|%s", linuxAppStack.JavaVersion)) } if linuxAppStack.PowerShellCoreVersion != "" { appSettings = updateOrAppendAppSettings(appSettings, "FUNCTIONS_WORKER_RUNTIME", "powershell", false) - expanded.LinuxFxVersion = utils.String(fmt.Sprintf("PowerShell|%s", linuxAppStack.PowerShellCoreVersion)) + expanded.LinuxFxVersion = utils.String(fmt.Sprintf("POWERSHELL|%s", linuxAppStack.PowerShellCoreVersion)) } if linuxAppStack.CustomHandler { @@ -1808,27 +1814,25 @@ func ExpandSiteConfigWindowsFunctionApp(siteConfig []SiteConfigWindowsFunctionAp if windowsAppStack.DotNetVersion != "" { if windowsAppStack.DotNetIsolated { appSettings = updateOrAppendAppSettings(appSettings, "FUNCTIONS_WORKER_RUNTIME", "dotnet-isolated", false) - expanded.WindowsFxVersion = utils.String(fmt.Sprintf("DOTNET-ISOLATED|%s", windowsAppStack.DotNetVersion)) } else { appSettings = updateOrAppendAppSettings(appSettings, "FUNCTIONS_WORKER_RUNTIME", "dotnet", false) - expanded.WindowsFxVersion = utils.String(fmt.Sprintf("DOTNET|%s", windowsAppStack.DotNetVersion)) } + expanded.NetFrameworkVersion = pointer.To(windowsAppStack.DotNetVersion) } if windowsAppStack.NodeVersion != "" { appSettings = updateOrAppendAppSettings(appSettings, "FUNCTIONS_WORKER_RUNTIME", "node", false) appSettings = updateOrAppendAppSettings(appSettings, "WEBSITE_NODE_DEFAULT_VERSION", windowsAppStack.NodeVersion, false) - expanded.WindowsFxVersion = utils.String(fmt.Sprintf("Node|%s", windowsAppStack.NodeVersion)) } if windowsAppStack.JavaVersion != "" { appSettings = updateOrAppendAppSettings(appSettings, "FUNCTIONS_WORKER_RUNTIME", "java", false) - expanded.WindowsFxVersion = utils.String(fmt.Sprintf("Java|%s", windowsAppStack.JavaVersion)) + expanded.JavaVersion = pointer.To(windowsAppStack.JavaVersion) } if windowsAppStack.PowerShellCoreVersion != "" { appSettings = updateOrAppendAppSettings(appSettings, "FUNCTIONS_WORKER_RUNTIME", "powershell", false) - expanded.WindowsFxVersion = utils.String(fmt.Sprintf("PowerShell|%s", windowsAppStack.PowerShellCoreVersion)) + expanded.PowerShellVersion = pointer.To(strings.TrimPrefix(windowsAppStack.PowerShellCoreVersion, "~")) } if windowsAppStack.CustomHandler { @@ -1994,16 +1998,23 @@ func FlattenSiteConfigLinuxFunctionApp(functionAppSiteConfig *web.SiteConfig) (* } if functionAppSiteConfig.Cors != nil { + corsEmpty := false 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 + if corsSettings.AllowedOrigins != nil { + if len(*corsSettings.AllowedOrigins) > 0 { + cors.AllowedOrigins = *corsSettings.AllowedOrigins + } else if !cors.SupportCredentials { + corsEmpty = true + } + } + if !corsEmpty { + result.Cors = []CorsSetting{cors} } - result.Cors = []CorsSetting{cors} } var appStack []ApplicationStackLinuxFunctionApp @@ -2072,26 +2083,41 @@ func FlattenSiteConfigWindowsFunctionApp(functionAppSiteConfig *web.SiteConfig) if functionAppSiteConfig.Cors != nil { corsSettings := functionAppSiteConfig.Cors + corsEmpty := false cors := CorsSetting{} if corsSettings.SupportCredentials != nil { cors.SupportCredentials = *corsSettings.SupportCredentials } - if corsSettings.AllowedOrigins != nil && len(*corsSettings.AllowedOrigins) != 0 { - cors.AllowedOrigins = *corsSettings.AllowedOrigins + if corsSettings.AllowedOrigins != nil { + if len(*corsSettings.AllowedOrigins) > 0 { + cors.AllowedOrigins = *corsSettings.AllowedOrigins + } else if !cors.SupportCredentials { + corsEmpty = true + } + } + + if !corsEmpty { + result.Cors = []CorsSetting{cors} } - 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) + powershellVersion := "" + if p := functionAppSiteConfig.PowerShellVersion; p != nil { + powershellVersion = *p + if powershellVersion == "~7" { + powershellVersion = "7" } - appStack = decoded } - result.ApplicationStack = appStack + + result.ApplicationStack = []ApplicationStackWindowsFunctionApp{{ + DotNetVersion: pointer.From(functionAppSiteConfig.NetFrameworkVersion), + DotNetIsolated: false, // set this later from app_settings + NodeVersion: "", // Need to get this from app_settings later + JavaVersion: pointer.From(functionAppSiteConfig.JavaVersion), + PowerShellCoreVersion: powershellVersion, + CustomHandler: false, // set this later from app_settings + }} return result, nil } diff --git a/internal/services/appservice/helpers/function_app_slot_schema.go b/internal/services/appservice/helpers/function_app_slot_schema.go index 2439c8ca7f0b..25995745a9b7 100644 --- a/internal/services/appservice/helpers/function_app_slot_schema.go +++ b/internal/services/appservice/helpers/function_app_slot_schema.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2021-02-01/web" // nolint: staticcheck + "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" apimValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/apimanagement/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" @@ -641,7 +642,7 @@ func ExpandSiteConfigWindowsFunctionAppSlot(siteConfig []SiteConfigWindowsFuncti 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("") + expanded.WindowsFxVersion = utils.String("") } appSettings := make([]web.NameValuePair, 0) @@ -715,67 +716,39 @@ func ExpandSiteConfigWindowsFunctionAppSlot(siteConfig []SiteConfigWindowsFuncti expanded.AppCommandLine = utils.String(windowsSlotSiteConfig.AppCommandLine) } - if metadata.ResourceData.HasChange("site_config.0.application_stack") && len(windowsSlotSiteConfig.ApplicationStack) > 0 { - if len(windowsSlotSiteConfig.ApplicationStack) > 0 { - windowsAppStack := windowsSlotSiteConfig.ApplicationStack[0] - if windowsAppStack.DotNetVersion != "" { - if windowsAppStack.DotNetIsolated { - appSettings = append(appSettings, web.NameValuePair{ - Name: utils.String("FUNCTIONS_WORKER_RUNTIME"), - Value: utils.String("dotnet-isolated"), - }) - expanded.WindowsFxVersion = utils.String(fmt.Sprintf("DOTNET-ISOLATED|%s", windowsAppStack.DotNetVersion)) - } else { - appSettings = append(appSettings, web.NameValuePair{ - Name: utils.String("FUNCTIONS_WORKER_RUNTIME"), - Value: utils.String("dotnet"), - }) - expanded.WindowsFxVersion = utils.String(fmt.Sprintf("DOTNET|%s", windowsAppStack.DotNetVersion)) - } + if len(windowsSlotSiteConfig.ApplicationStack) > 0 { + windowsAppStack := windowsSlotSiteConfig.ApplicationStack[0] + if windowsAppStack.DotNetVersion != "" { + if windowsAppStack.DotNetIsolated { + appSettings = updateOrAppendAppSettings(appSettings, "FUNCTIONS_WORKER_RUNTIME", "dotnet-isolated", false) + } else { + appSettings = updateOrAppendAppSettings(appSettings, "FUNCTIONS_WORKER_RUNTIME", "dotnet", false) } + expanded.NetFrameworkVersion = pointer.To(windowsAppStack.DotNetVersion) + } - if windowsAppStack.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(windowsAppStack.NodeVersion), - }) - expanded.WindowsFxVersion = utils.String(fmt.Sprintf("Node|%s", windowsAppStack.NodeVersion)) - } + if windowsAppStack.NodeVersion != "" { + appSettings = updateOrAppendAppSettings(appSettings, "FUNCTIONS_WORKER_RUNTIME", "node", false) + appSettings = updateOrAppendAppSettings(appSettings, "WEBSITE_NODE_DEFAULT_VERSION", windowsAppStack.NodeVersion, false) + } - if windowsAppStack.JavaVersion != "" { - appSettings = append(appSettings, web.NameValuePair{ - Name: utils.String("FUNCTIONS_WORKER_RUNTIME"), - Value: utils.String("java"), - }) - expanded.WindowsFxVersion = utils.String(fmt.Sprintf("Java|%s", windowsAppStack.JavaVersion)) - } + if windowsAppStack.JavaVersion != "" { + appSettings = updateOrAppendAppSettings(appSettings, "FUNCTIONS_WORKER_RUNTIME", "java", false) + expanded.JavaVersion = pointer.To(windowsAppStack.JavaVersion) + } - if windowsAppStack.PowerShellCoreVersion != "" { - appSettings = append(appSettings, web.NameValuePair{ - Name: utils.String("FUNCTIONS_WORKER_RUNTIME"), - Value: utils.String("powershell"), - }) - expanded.WindowsFxVersion = utils.String(fmt.Sprintf("PowerShell|%s", windowsAppStack.PowerShellCoreVersion)) - } + if windowsAppStack.PowerShellCoreVersion != "" { + appSettings = updateOrAppendAppSettings(appSettings, "FUNCTIONS_WORKER_RUNTIME", "powershell", false) + expanded.PowerShellVersion = pointer.To(strings.TrimPrefix(windowsAppStack.PowerShellCoreVersion, "~")) + } - if windowsAppStack.CustomHandler { - appSettings = append(appSettings, web.NameValuePair{ - Name: utils.String("FUNCTIONS_WORKER_RUNTIME"), - Value: utils.String("custom"), - }) - expanded.WindowsFxVersion = utils.String("") // Custom needs an explicit empty string here - } - } else { - appSettings = append(appSettings, web.NameValuePair{ - Name: utils.String("FUNCTIONS_WORKER_RUNTIME"), - Value: utils.String(""), - }) - expanded.WindowsFxVersion = utils.String("") + if windowsAppStack.CustomHandler { + appSettings = updateOrAppendAppSettings(appSettings, "FUNCTIONS_WORKER_RUNTIME", "custom", false) + expanded.WindowsFxVersion = utils.String("") // Custom needs an explicit empty string here } + } else { + appSettings = updateOrAppendAppSettings(appSettings, "FUNCTIONS_WORKER_RUNTIME", "", true) + expanded.WindowsFxVersion = utils.String("") } expanded.VnetRouteAllEnabled = utils.Bool(windowsSlotSiteConfig.VnetRouteAllEnabled) @@ -915,27 +888,41 @@ func FlattenSiteConfigWindowsFunctionAppSlot(functionAppSlotSiteConfig *web.Site } if functionAppSlotSiteConfig.Cors != nil { + corsEmpty := false corsSettings := functionAppSlotSiteConfig.Cors cors := CorsSetting{} if corsSettings.SupportCredentials != nil { cors.SupportCredentials = *corsSettings.SupportCredentials } - if corsSettings.AllowedOrigins != nil && len(*corsSettings.AllowedOrigins) != 0 { - cors.AllowedOrigins = *corsSettings.AllowedOrigins + if corsSettings.AllowedOrigins != nil { + if len(*corsSettings.AllowedOrigins) > 0 { + cors.AllowedOrigins = *corsSettings.AllowedOrigins + } else if !cors.SupportCredentials { + corsEmpty = true + } + } + if !corsEmpty { + result.Cors = []CorsSetting{cors} } - result.Cors = []CorsSetting{cors} } - var appStack []ApplicationStackWindowsFunctionApp - if functionAppSlotSiteConfig.WindowsFxVersion != nil { - decoded, err := DecodeFunctionAppWindowsFxVersion(*functionAppSlotSiteConfig.WindowsFxVersion) - if err != nil { - return nil, fmt.Errorf("flattening site config: %s", err) + powershellVersion := "" + if p := functionAppSlotSiteConfig.PowerShellVersion; p != nil { + powershellVersion = *p + if powershellVersion == "~7" { + powershellVersion = "7" } - appStack = decoded } - result.ApplicationStack = appStack + + result.ApplicationStack = []ApplicationStackWindowsFunctionApp{{ + DotNetVersion: pointer.From(functionAppSlotSiteConfig.NetFrameworkVersion), + DotNetIsolated: false, // Note: this is set later from app_settings.FUNCTIONS_WORKER_RUNTIME in unpackWindowsFunctionAppSettings + NodeVersion: "", // Note: this will be set from app_settings later in unpackWindowsFunctionAppSettings + JavaVersion: pointer.From(functionAppSlotSiteConfig.JavaVersion), + PowerShellCoreVersion: powershellVersion, + CustomHandler: false, // Note: this is set later from app_settings + }} return result, nil } @@ -1255,15 +1242,22 @@ func FlattenSiteConfigLinuxFunctionAppSlot(functionAppSlotSiteConfig *web.SiteCo if functionAppSlotSiteConfig.Cors != nil { corsSettings := functionAppSlotSiteConfig.Cors + corsEmpty := false cors := CorsSetting{} if corsSettings.SupportCredentials != nil { cors.SupportCredentials = *corsSettings.SupportCredentials } - if corsSettings.AllowedOrigins != nil && len(*corsSettings.AllowedOrigins) != 0 { - cors.AllowedOrigins = *corsSettings.AllowedOrigins + if corsSettings.AllowedOrigins != nil { + if len(*corsSettings.AllowedOrigins) > 0 { + cors.AllowedOrigins = *corsSettings.AllowedOrigins + } else if !cors.SupportCredentials { + corsEmpty = true + } + } + if !corsEmpty { + result.Cors = []CorsSetting{cors} } - result.Cors = []CorsSetting{cors} } var appStack []ApplicationStackLinuxFunctionApp diff --git a/internal/services/appservice/helpers/fx_strings.go b/internal/services/appservice/helpers/fx_strings.go index eae7b2db00e6..b5a8180da6f8 100644 --- a/internal/services/appservice/helpers/fx_strings.go +++ b/internal/services/appservice/helpers/fx_strings.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/terraform-provider-azurerm/utils" ) @@ -18,19 +19,44 @@ func decodeApplicationStackLinux(fxString string) ApplicationStackLinux { case "DOTNETCORE", "DOTNET": result.NetFrameworkVersion = parts[1] + case "GO": + result.GoVersion = parts[1] + case "NODE": result.NodeVersion = parts[1] - case "JAVA", "TOMCAT", "JBOSSEAP": - result.JavaServer = parts[0] + case LinuxJavaServerJava: + result.JavaServer = LinuxJavaServerJava + javaParts := strings.Split(parts[1], "-") + if strings.HasPrefix(parts[1], "8") { + result.JavaVersion = "8" + } + if strings.HasPrefix(javaParts[0], "11") { + result.JavaVersion = "11" + } + if strings.HasPrefix(javaParts[0], "17") { + result.JavaVersion = "17" + } + result.JavaServerVersion = javaParts[0] + + case LinuxJavaServerTomcat: + result.JavaServer = LinuxJavaServerTomcat javaParts := strings.Split(parts[1], "-") if len(javaParts) == 2 { - // e.g. 8-jre8 result.JavaServerVersion = javaParts[0] - result.JavaVersion = javaParts[1] - } else { - // e.g. 8u242 or 11.0.9 - result.JavaVersion = parts[1] + javaVersion := strings.TrimPrefix(javaParts[1], "jre") + javaVersion = strings.TrimPrefix(javaVersion, "java") + result.JavaVersion = javaVersion + } + + case LinuxJavaServerJboss: + result.JavaServer = LinuxJavaServerJboss + javaParts := strings.Split(parts[1], "-") + if len(javaParts) == 2 { + result.JavaServerVersion = javaParts[0] + javaVersion := strings.TrimPrefix(javaParts[1], "jre") + javaVersion = strings.TrimPrefix(javaVersion, "java") + result.JavaVersion = javaVersion } case "PHP": @@ -167,73 +193,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 != "": - if appStack.DotNetIsolated { - appType = "DotNet-Isolated" - } else { - appType = "DotNet" +func JavaLinuxFxStringBuilder(javaMajorVersion, javaServer, javaServerVersion string) (*string, error) { + switch javaMajorVersion { + case "8": + { + switch javaServer { + case LinuxJavaServerJava: + if strings.Contains(javaServerVersion, "u") { + return pointer.To(fmt.Sprintf("%s|%s", LinuxJavaServerJava, javaServerVersion)), nil // e.g. JAVA|8u302 + } else { + return pointer.To(fmt.Sprintf("%s|%s-jre8", LinuxJavaServerJava, javaServerVersion)), nil // e.g. "JAVA|8-jre8" + } + case LinuxJavaServerTomcat: + if len(strings.Split(javaServerVersion, ".")) == 3 { + return pointer.To(fmt.Sprintf("%s|%s-java8", LinuxJavaServerTomcat, javaServerVersion)), nil // e.g. TOMCAT|10.0.20-java8 + } else { + return pointer.To(fmt.Sprintf("%s|%s-jre8", LinuxJavaServerTomcat, javaServerVersion)), nil // e.g. TOMCAT|10.0-jre8 + } + case LinuxJavaServerJboss: + return pointer.To(fmt.Sprintf("%s|%s-java8", LinuxJavaServerJboss, javaServerVersion)), nil + } + } + case "11": + switch javaServer { + case LinuxJavaServerJava: + if len(strings.Split(javaServerVersion, ".")) == 3 { + return pointer.To(fmt.Sprintf("%s|%s", LinuxJavaServerJava, javaServerVersion)), nil // e.g. JAVA|11.0.13 + } else { + return pointer.To(fmt.Sprintf("%s|%s-java11", LinuxJavaServerJava, javaServerVersion)), nil // e.g.JAVA|11-java1 + } + case LinuxJavaServerTomcat: + return pointer.To(fmt.Sprintf("%s|%s-java11", LinuxJavaServerTomcat, javaServerVersion)), nil // e.g. TOMCAT|10.0-java11 and TOMCAT|10.0.20-java11 + + case LinuxJavaServerJboss: + return pointer.To(fmt.Sprintf("%s|%s-java11", LinuxJavaServerJboss, javaServerVersion)), nil // e.g. TOMCAT|10.0-java11 and TOMCAT|10.0.20-java11// e.g. JBOSSEAP|7-java11 / JBOSSEAP|7.4.2-java11 } - 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 "dotnet-isolated": - appStack := ApplicationStackWindowsFunctionApp{DotNetVersion: parts[1], DotNetIsolated: true} - result = append(result, appStack) - case "node": - appStack := ApplicationStackWindowsFunctionApp{NodeVersion: parts[1]} - result = append(result, appStack) + case "17": + switch javaServer { + case LinuxJavaServerJava: + if len(strings.Split(javaServerVersion, ".")) == 3 { + return pointer.To(fmt.Sprintf("%s|%s", LinuxJavaServerJava, javaServerVersion)), nil // "JAVA|17.0.2" + } else { + return pointer.To(fmt.Sprintf("%s|%s-java17", LinuxJavaServerJava, javaServerVersion)), nil // "JAVA|17-java17" + } + + case LinuxJavaServerTomcat: + return pointer.To(fmt.Sprintf("%s|%s-java17", LinuxJavaServerTomcat, javaServerVersion)), nil // e,g, TOMCAT|10.0-java17 / TOMCAT|10.0.20-java17 + case LinuxJavaServerJboss: + return nil, fmt.Errorf("java 17 is not supported on %s", LinuxJavaServerJboss) + default: + return pointer.To(fmt.Sprintf("%s|%s-java17", javaServer, javaServerVersion)), nil + } - case "java": - appStack := ApplicationStackWindowsFunctionApp{JavaVersion: parts[1]} - result = append(result, appStack) + default: + return pointer.To(fmt.Sprintf("%s|%s-%s", javaServer, javaServerVersion, javaMajorVersion)), nil - case "powershell": - appStack := ApplicationStackWindowsFunctionApp{PowerShellCoreVersion: parts[1]} - result = append(result, appStack) } - - return result, nil + return nil, fmt.Errorf("unsupported combination of `java_version`, `java_server`, and `java_server_version`") } diff --git a/internal/services/appservice/helpers/linux_web_app_schema.go b/internal/services/appservice/helpers/linux_web_app_schema.go new file mode 100644 index 000000000000..56af60e5a14f --- /dev/null +++ b/internal/services/appservice/helpers/linux_web_app_schema.go @@ -0,0 +1,1138 @@ +package helpers + +import ( + "fmt" + "strconv" + "strings" + + "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2021-02-01/web" // nolint: staticcheck + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/apimanagement/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +type SiteConfigLinux struct { + AlwaysOn bool `tfschema:"always_on"` + ApiManagementConfigId string `tfschema:"api_management_api_id"` + ApiDefinition string `tfschema:"api_definition_url"` + AppCommandLine string `tfschema:"app_command_line"` + AutoHeal bool `tfschema:"auto_heal_enabled"` + AutoHealSettings []AutoHealSettingLinux `tfschema:"auto_heal_setting"` + UseManagedIdentityACR bool `tfschema:"container_registry_use_managed_identity"` + ContainerRegistryMSI string `tfschema:"container_registry_managed_identity_client_id"` + DefaultDocuments []string `tfschema:"default_documents"` + Http2Enabled bool `tfschema:"http2_enabled"` + IpRestriction []IpRestriction `tfschema:"ip_restriction"` + ScmUseMainIpRestriction bool `tfschema:"scm_use_main_ip_restriction"` + ScmIpRestriction []IpRestriction `tfschema:"scm_ip_restriction"` + LoadBalancing string `tfschema:"load_balancing_mode"` + LocalMysql bool `tfschema:"local_mysql_enabled"` + ManagedPipelineMode string `tfschema:"managed_pipeline_mode"` + RemoteDebugging bool `tfschema:"remote_debugging_enabled"` + RemoteDebuggingVersion string `tfschema:"remote_debugging_version"` + ScmType string `tfschema:"scm_type"` + 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:"worker_count"` + ApplicationStack []ApplicationStackLinux `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_enabled"` + LinuxFxVersion string `tfschema:"linux_fx_version"` + VnetRouteAllEnabled bool `tfschema:"vnet_route_all_enabled"` + // SiteLimits []SiteLimitsSettings `tfschema:"site_limits"` // TODO - New block to (possibly) support? No way to configure this in the portal? +} + +func SiteConfigSchemaLinux() *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, + Default: true, + }, + + "api_management_api_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validate.ApiID, + }, + + "api_definition_url": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.IsURLWithHTTPorHTTPS, + }, + + "app_command_line": { + Type: pluginsdk.TypeString, + Optional: true, + }, + + "application_stack": linuxApplicationStackSchema(), + + "auto_heal_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + RequiredWith: []string{ + "site_config.0.auto_heal_setting", + }, + }, + + "auto_heal_setting": autoHealSettingSchemaLinux(), + + "container_registry_use_managed_identity": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "container_registry_managed_identity_client_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.IsUUID, + }, + + "default_documents": { + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "http2_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "ip_restriction": IpRestrictionSchema(), + + "scm_use_main_ip_restriction": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "scm_ip_restriction": IpRestrictionSchema(), + + "local_mysql_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "load_balancing_mode": { + Type: pluginsdk.TypeString, + Optional: true, + Default: "LeastRequests", + ValidateFunc: validation.StringInSlice([]string{ + "LeastRequests", // Service default + "WeightedRoundRobin", + "LeastResponseTime", + "WeightedTotalTraffic", + "RequestHash", + "PerSiteRoundRobin", + }, false), + }, + + "managed_pipeline_mode": { + Type: pluginsdk.TypeString, + Optional: true, + Default: string(web.ManagedPipelineModeIntegrated), + ValidateFunc: validation.StringInSlice([]string{ + string(web.ManagedPipelineModeClassic), + string(web.ManagedPipelineModeIntegrated), + }, false), + }, + + "remote_debugging_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "remote_debugging_version": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + "VS2017", + "VS2019", + }, false), + }, + + "scm_type": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "use_32_bit_worker": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + }, + + "websockets_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: 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), + }, + + "health_check_path": { + Type: pluginsdk.TypeString, + Optional: true, + }, + + "health_check_eviction_time_in_min": { + 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`", + }, + + "worker_count": { + Type: pluginsdk.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: validation.IntBetween(1, 100), + }, + + "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), + }, + + "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), + }, + + "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_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "linux_fx_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + } +} + +func SiteConfigSchemaLinuxComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "always_on": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "api_management_api_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "api_definition_url": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "app_command_line": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "application_stack": linuxApplicationStackSchemaComputed(), + + "auto_heal_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "auto_heal_setting": autoHealSettingSchemaLinuxComputed(), + + "container_registry_use_managed_identity": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "container_registry_managed_identity_client_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "default_documents": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "http2_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "ip_restriction": IpRestrictionSchemaComputed(), + + "scm_use_main_ip_restriction": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "scm_ip_restriction": IpRestrictionSchemaComputed(), + + "local_mysql_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "load_balancing_mode": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "managed_pipeline_mode": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "remote_debugging_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "remote_debugging_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "scm_type": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "use_32_bit_worker": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "websockets_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "ftps_state": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "health_check_path": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "health_check_eviction_time_in_min": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + + "worker_count": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + + "minimum_tls_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "scm_minimum_tls_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "cors": CorsSettingsSchemaComputed(), + + "detailed_error_logging_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "linux_fx_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "vnet_route_all_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + }, + }, + } +} + +type AutoHealSettingLinux struct { + Triggers []AutoHealTriggerLinux `tfschema:"trigger"` + Actions []AutoHealActionLinux `tfschema:"action"` +} + +type AutoHealTriggerLinux struct { + Requests []AutoHealRequestTrigger `tfschema:"requests"` + StatusCodes []AutoHealStatusCodeTrigger `tfschema:"status_code"` // 0 or more, ranges split by `-`, ranges cannot use sub-status or win32 code + SlowRequests []AutoHealSlowRequest `tfschema:"slow_request"` +} + +type AutoHealActionLinux struct { + ActionType string `tfschema:"action_type"` // Enum - Only `Recycle` allowed + MinimumProcessTime string `tfschema:"minimum_process_execution_time"` // Minimum uptime for process before action will trigger +} + +func autoHealSettingSchemaLinux() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "trigger": autoHealTriggerSchemaLinux(), + + "action": autoHealActionSchemaLinux(), + }, + }, + RequiredWith: []string{ + "site_config.0.auto_heal_enabled", + }, + } +} + +func autoHealSettingSchemaLinuxComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "trigger": autoHealTriggerSchemaLinuxComputed(), + + "action": autoHealActionSchemaLinuxComputed(), + }, + }, + } +} + +func autoHealActionSchemaLinux() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "action_type": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.AutoHealActionTypeRecycle), + }, false), + }, + + "minimum_process_execution_time": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + // ValidateFunc: // TODO - Time in hh:mm:ss, because why not... + }, + }, + }, + } +} + +func autoHealActionSchemaLinuxComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "action_type": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "minimum_process_execution_time": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + } +} + +// (@jackofallops) - trigger schemas intentionally left long-hand for now +func autoHealTriggerSchemaLinux() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "requests": { + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "count": { + Type: pluginsdk.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + + "interval": { + Type: pluginsdk.TypeString, + Required: true, + // ValidateFunc: validation.IsRFC3339Time, // TODO should be hh:mm:ss - This is too loose, need to improve? + }, + }, + }, + }, + + "status_code": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "status_code_range": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: nil, // TODO - status code range validation + }, + + "count": { + Type: pluginsdk.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + + "interval": { + Type: pluginsdk.TypeString, + Required: true, + // ValidateFunc: validation.IsRFC3339Time, + }, + + "sub_status": { + Type: pluginsdk.TypeInt, + Optional: true, + ValidateFunc: nil, // TODO - no docs on this, needs investigation + }, + + "win32_status": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: nil, // TODO - no docs on this, needs investigation + }, + + "path": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, + }, + + "slow_request": { + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "time_taken": { + Type: pluginsdk.TypeString, + Required: true, + // ValidateFunc: validation.IsRFC3339Time, + }, + + "interval": { + Type: pluginsdk.TypeString, + Required: true, + // ValidateFunc: validation.IsRFC3339Time, + }, + + "count": { + Type: pluginsdk.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + + "path": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, + }, + }, + }, + } +} + +func autoHealTriggerSchemaLinuxComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "requests": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "count": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + + "interval": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + + "status_code": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "status_code_range": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "count": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + + "interval": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "sub_status": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + + "win32_status": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "path": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + + "slow_request": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "time_taken": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "interval": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "count": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + + "path": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + } +} + +func ExpandSiteConfigLinux(siteConfig []SiteConfigLinux, existing *web.SiteConfig, metadata sdk.ResourceMetaData, servicePlan web.AppServicePlan) (*web.SiteConfig, error) { + if len(siteConfig) == 0 { + return nil, nil + } + expanded := &web.SiteConfig{} + if existing != nil { + expanded = existing + } + + linuxSiteConfig := siteConfig[0] + + if servicePlan.Sku != nil && servicePlan.Sku.Name != nil { + if isFreeOrSharedServicePlan(*servicePlan.Sku.Name) { + if linuxSiteConfig.AlwaysOn { + return nil, fmt.Errorf("always_on cannot be set to true when using Free, F1, D1 Sku") + } + if expanded.AlwaysOn != nil && *expanded.AlwaysOn { + return nil, fmt.Errorf("always_on feature has to be turned off before switching to a free/shared Sku") + } + } + } + expanded.AlwaysOn = pointer.To(linuxSiteConfig.AlwaysOn) + + if metadata.ResourceData.HasChange("site_config.0.api_management_api_id") { + expanded.APIManagementConfig = &web.APIManagementConfig{ + ID: pointer.To(linuxSiteConfig.ApiManagementConfigId), + } + } + + if metadata.ResourceData.HasChange("site_config.0.api_definition_url") { + expanded.APIDefinition = &web.APIDefinitionInfo{ + URL: pointer.To(linuxSiteConfig.ApiDefinition), + } + } + + if metadata.ResourceData.HasChange("site_config.0.app_command_line") { + expanded.AppCommandLine = pointer.To(linuxSiteConfig.AppCommandLine) + } + + if metadata.ResourceData.HasChange("site_config.0.application_stack") { + if len(linuxSiteConfig.ApplicationStack) == 1 { + linuxAppStack := linuxSiteConfig.ApplicationStack[0] + if linuxAppStack.NetFrameworkVersion != "" { + expanded.LinuxFxVersion = pointer.To(fmt.Sprintf("DOTNETCORE|%s", linuxAppStack.NetFrameworkVersion)) + } + + if linuxAppStack.GoVersion != "" { + expanded.LinuxFxVersion = pointer.To(fmt.Sprintf("GO|%s", linuxAppStack.GoVersion)) + } + + if linuxAppStack.PhpVersion != "" { + expanded.LinuxFxVersion = pointer.To(fmt.Sprintf("PHP|%s", linuxAppStack.PhpVersion)) + } + + if linuxAppStack.NodeVersion != "" { + expanded.LinuxFxVersion = pointer.To(fmt.Sprintf("NODE|%s", linuxAppStack.NodeVersion)) + } + + if linuxAppStack.RubyVersion != "" { + expanded.LinuxFxVersion = pointer.To(fmt.Sprintf("RUBY|%s", linuxAppStack.RubyVersion)) + } + + if linuxAppStack.PythonVersion != "" { + expanded.LinuxFxVersion = pointer.To(fmt.Sprintf("PYTHON|%s", linuxAppStack.PythonVersion)) + } + + if linuxAppStack.JavaServer != "" { + javaString, err := JavaLinuxFxStringBuilder(linuxAppStack.JavaVersion, linuxAppStack.JavaServer, linuxAppStack.JavaServerVersion) + if err != nil { + return nil, fmt.Errorf("could not build linuxFxVersion string: %+v", err) + } + expanded.LinuxFxVersion = javaString + } + + if linuxAppStack.DockerImage != "" { + expanded.LinuxFxVersion = pointer.To(fmt.Sprintf("DOCKER|%s:%s", linuxAppStack.DockerImage, linuxAppStack.DockerImageTag)) + } + } else { + expanded.LinuxFxVersion = pointer.To("") + } + } + + expanded.AcrUseManagedIdentityCreds = pointer.To(linuxSiteConfig.UseManagedIdentityACR) + + if metadata.ResourceData.HasChange("site_config.0.container_registry_managed_identity_client_id") { + expanded.AcrUserManagedIdentityID = pointer.To(linuxSiteConfig.ContainerRegistryMSI) + } + + if metadata.ResourceData.HasChange("site_config.0.default_documents") { + expanded.DefaultDocuments = &linuxSiteConfig.DefaultDocuments + } + + expanded.HTTP20Enabled = pointer.To(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 + } + + expanded.ScmIPSecurityRestrictionsUseMain = pointer.To(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 + } + + expanded.LocalMySQLEnabled = pointer.To(linuxSiteConfig.LocalMysql) + + 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) + } + + expanded.RemoteDebuggingEnabled = pointer.To(linuxSiteConfig.RemoteDebugging) + + if metadata.ResourceData.HasChange("site_config.0.remote_debugging_version") { + expanded.RemoteDebuggingVersion = pointer.To(linuxSiteConfig.RemoteDebuggingVersion) + } + + expanded.Use32BitWorkerProcess = pointer.To(linuxSiteConfig.Use32BitWorker) + + expanded.WebSocketsEnabled = pointer.To(linuxSiteConfig.WebSockets) + + if metadata.ResourceData.HasChange("site_config.0.ftps_state") { + expanded.FtpsState = web.FtpsState(linuxSiteConfig.FtpsState) + } + + if metadata.ResourceData.HasChange("site_config.0.health_check_path") { + expanded.HealthCheckPath = pointer.To(linuxSiteConfig.HealthCheckPath) + } + + if metadata.ResourceData.HasChange("site_config.0.worker_count") { + expanded.NumberOfWorkers = pointer.To(int32(linuxSiteConfig.NumberOfWorkers)) + } + + if metadata.ResourceData.HasChange("site_config.0.minimum_tls_version") { + expanded.MinTLSVersion = web.SupportedTLSVersions(linuxSiteConfig.MinTlsVersion) + } + + 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) + if cors == nil { + cors = &web.CorsSettings{ + AllowedOrigins: &[]string{}, + } + } + expanded.Cors = cors + } + + expanded.AutoHealEnabled = pointer.To(linuxSiteConfig.AutoHeal) + + if metadata.ResourceData.HasChange("site_config.0.auto_heal_setting") { + expanded.AutoHealRules = expandAutoHealSettingsLinux(linuxSiteConfig.AutoHealSettings) + } + + if metadata.ResourceData.HasChange("site_config.0.vnet_route_all_enabled") { + expanded.VnetRouteAllEnabled = pointer.To(linuxSiteConfig.VnetRouteAllEnabled) + } + + return expanded, nil +} + +func FlattenSiteConfigLinux(appSiteConfig *web.SiteConfig, healthCheckCount *int) []SiteConfigLinux { + if appSiteConfig == nil { + return nil + } + + siteConfig := SiteConfigLinux{ + AlwaysOn: pointer.From(appSiteConfig.AlwaysOn), + AppCommandLine: pointer.From(appSiteConfig.AppCommandLine), + AutoHeal: pointer.From(appSiteConfig.AutoHealEnabled), + AutoHealSettings: flattenAutoHealSettingsLinux(appSiteConfig.AutoHealRules), + ContainerRegistryMSI: pointer.From(appSiteConfig.AcrUserManagedIdentityID), + DetailedErrorLogging: pointer.From(appSiteConfig.DetailedErrorLoggingEnabled), + Http2Enabled: pointer.From(appSiteConfig.HTTP20Enabled), + IpRestriction: FlattenIpRestrictions(appSiteConfig.IPSecurityRestrictions), + ManagedPipelineMode: string(appSiteConfig.ManagedPipelineMode), + ScmType: string(appSiteConfig.ScmType), + FtpsState: string(appSiteConfig.FtpsState), + HealthCheckPath: pointer.From(appSiteConfig.HealthCheckPath), + HealthCheckEvictionTime: pointer.From(healthCheckCount), + LoadBalancing: string(appSiteConfig.LoadBalancing), + LocalMysql: pointer.From(appSiteConfig.LocalMySQLEnabled), + MinTlsVersion: string(appSiteConfig.MinTLSVersion), + NumberOfWorkers: int(pointer.From(appSiteConfig.NumberOfWorkers)), + RemoteDebugging: pointer.From(appSiteConfig.RemoteDebuggingEnabled), + RemoteDebuggingVersion: strings.ToUpper(pointer.From(appSiteConfig.RemoteDebuggingVersion)), + ScmIpRestriction: FlattenIpRestrictions(appSiteConfig.ScmIPSecurityRestrictions), + ScmMinTlsVersion: string(appSiteConfig.ScmMinTLSVersion), + ScmUseMainIpRestriction: pointer.From(appSiteConfig.ScmIPSecurityRestrictionsUseMain), + Use32BitWorker: pointer.From(appSiteConfig.Use32BitWorkerProcess), + UseManagedIdentityACR: pointer.From(appSiteConfig.AcrUseManagedIdentityCreds), + WebSockets: pointer.From(appSiteConfig.WebSocketsEnabled), + VnetRouteAllEnabled: pointer.From(appSiteConfig.VnetRouteAllEnabled), + } + + if appSiteConfig.APIManagementConfig != nil && appSiteConfig.APIManagementConfig.ID != nil { + siteConfig.ApiManagementConfigId = *appSiteConfig.APIManagementConfig.ID + } + + if appSiteConfig.APIDefinition != nil && appSiteConfig.APIDefinition.URL != nil { + siteConfig.ApiDefinition = *appSiteConfig.APIDefinition.URL + } + + if appSiteConfig.DefaultDocuments != nil { + siteConfig.DefaultDocuments = *appSiteConfig.DefaultDocuments + } + + if appSiteConfig.LinuxFxVersion != nil { + var linuxAppStack ApplicationStackLinux + siteConfig.LinuxFxVersion = *appSiteConfig.LinuxFxVersion + // Decode the string to docker values + linuxAppStack = decodeApplicationStackLinux(siteConfig.LinuxFxVersion) + siteConfig.ApplicationStack = []ApplicationStackLinux{linuxAppStack} + } + + if appSiteConfig.Cors != nil { + corsEmpty := false + corsSettings := appSiteConfig.Cors + cors := CorsSetting{} + if corsSettings.SupportCredentials != nil { + cors.SupportCredentials = *corsSettings.SupportCredentials + } + + if corsSettings.AllowedOrigins != nil { + if len(*corsSettings.AllowedOrigins) > 0 { + cors.AllowedOrigins = *corsSettings.AllowedOrigins + } else if !cors.SupportCredentials { + corsEmpty = true + } + } + if !corsEmpty { + siteConfig.Cors = []CorsSetting{cors} + } + } + + return []SiteConfigLinux{siteConfig} +} + +func expandAutoHealSettingsLinux(autoHealSettings []AutoHealSettingLinux) *web.AutoHealRules { + if len(autoHealSettings) == 0 { + return nil + } + + result := &web.AutoHealRules{ + Triggers: &web.AutoHealTriggers{}, + Actions: &web.AutoHealActions{}, + } + + autoHeal := autoHealSettings[0] + + triggers := autoHeal.Triggers[0] + if len(triggers.Requests) == 1 { + result.Triggers.Requests = &web.RequestsBasedTrigger{ + Count: pointer.To(int32(triggers.Requests[0].Count)), + TimeInterval: pointer.To(triggers.Requests[0].Interval), + } + } + + if len(triggers.SlowRequests) == 1 { + result.Triggers.SlowRequests = &web.SlowRequestsBasedTrigger{ + TimeTaken: pointer.To(triggers.SlowRequests[0].TimeTaken), + TimeInterval: pointer.To(triggers.SlowRequests[0].Interval), + Count: pointer.To(int32(triggers.SlowRequests[0].Count)), + } + if triggers.SlowRequests[0].Path != "" { + result.Triggers.SlowRequests.Path = pointer.To(triggers.SlowRequests[0].Path) + } + } + + if len(triggers.StatusCodes) > 0 { + statusCodeTriggers := make([]web.StatusCodesBasedTrigger, 0) + statusCodeRangeTriggers := make([]web.StatusCodesRangeBasedTrigger, 0) + for _, s := range triggers.StatusCodes { + statusCodeTrigger := web.StatusCodesBasedTrigger{} + statusCodeRangeTrigger := web.StatusCodesRangeBasedTrigger{} + parts := strings.Split(s.StatusCodeRange, "-") + if len(parts) == 2 { + statusCodeRangeTrigger.StatusCodes = pointer.To(s.StatusCodeRange) + statusCodeRangeTrigger.Count = pointer.To(int32(s.Count)) + statusCodeRangeTrigger.TimeInterval = pointer.To(s.Interval) + if s.Path != "" { + statusCodeRangeTrigger.Path = pointer.To(s.Path) + } + statusCodeRangeTriggers = append(statusCodeRangeTriggers, statusCodeRangeTrigger) + } else { + statusCode, err := strconv.Atoi(s.StatusCodeRange) + if err == nil { + statusCodeTrigger.Status = pointer.To(int32(statusCode)) + } + statusCodeTrigger.Count = pointer.To(int32(s.Count)) + statusCodeTrigger.TimeInterval = pointer.To(s.Interval) + if s.Path != "" { + statusCodeTrigger.Path = pointer.To(s.Path) + } + statusCodeTriggers = append(statusCodeTriggers, statusCodeTrigger) + } + } + result.Triggers.StatusCodes = &statusCodeTriggers + result.Triggers.StatusCodesRange = &statusCodeRangeTriggers + } + + action := autoHeal.Actions[0] + result.Actions.ActionType = web.AutoHealActionType(action.ActionType) + result.Actions.MinProcessExecutionTime = pointer.To(action.MinimumProcessTime) + + return result +} + +func flattenAutoHealSettingsLinux(autoHealRules *web.AutoHealRules) []AutoHealSettingLinux { + if autoHealRules == nil { + return nil + } + + result := AutoHealSettingLinux{} + + // Triggers + if autoHealRules.Triggers != nil { + resultTrigger := AutoHealTriggerLinux{} + triggers := *autoHealRules.Triggers + if triggers.Requests != nil { + count := 0 + if triggers.Requests.Count != nil { + count = int(*triggers.Requests.Count) + } + resultTrigger.Requests = []AutoHealRequestTrigger{{ + Count: count, + Interval: pointer.From(triggers.Requests.TimeInterval), + }} + } + + statusCodeTriggers := make([]AutoHealStatusCodeTrigger, 0) + if triggers.StatusCodes != nil { + for _, s := range *triggers.StatusCodes { + t := AutoHealStatusCodeTrigger{ + Interval: pointer.From(s.TimeInterval), + Path: pointer.From(s.Path), + } + + if s.Status != nil { + t.StatusCodeRange = strconv.Itoa(int(*s.Status)) + } + + if s.Count != nil { + t.Count = int(*s.Count) + } + + if s.SubStatus != nil { + t.SubStatus = int(*s.SubStatus) + } + statusCodeTriggers = append(statusCodeTriggers, t) + } + } + if triggers.StatusCodesRange != nil { + for _, s := range *triggers.StatusCodesRange { + t := AutoHealStatusCodeTrigger{ + Interval: pointer.From(s.TimeInterval), + Path: pointer.From(s.Path), + } + if s.Count != nil { + t.Count = int(*s.Count) + } + + if s.StatusCodes != nil { + t.StatusCodeRange = *s.StatusCodes + } + statusCodeTriggers = append(statusCodeTriggers, t) + } + } + resultTrigger.StatusCodes = statusCodeTriggers + + slowRequestTriggers := make([]AutoHealSlowRequest, 0) + if triggers.SlowRequests != nil { + slowRequestTriggers = append(slowRequestTriggers, AutoHealSlowRequest{ + TimeTaken: pointer.From(triggers.SlowRequests.TimeTaken), + Interval: pointer.From(triggers.SlowRequests.TimeInterval), + Count: int(pointer.From(triggers.SlowRequests.Count)), + Path: pointer.From(triggers.SlowRequests.Path), + }) + } + resultTrigger.SlowRequests = slowRequestTriggers + result.Triggers = []AutoHealTriggerLinux{resultTrigger} + } + + // Actions + if autoHealRules.Actions != nil { + actions := *autoHealRules.Actions + + result.Actions = []AutoHealActionLinux{{ + ActionType: string(actions.ActionType), + MinimumProcessTime: pointer.From(actions.MinProcessExecutionTime), + }} + } + + if result.Triggers != nil || result.Actions != nil { + return []AutoHealSettingLinux{result} + } + + return nil +} diff --git a/internal/services/appservice/helpers/shared_schema.go b/internal/services/appservice/helpers/shared_schema.go index 9aeda6a69c89..9e2e0c7f54a3 100644 --- a/internal/services/appservice/helpers/shared_schema.go +++ b/internal/services/appservice/helpers/shared_schema.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2021-02-01/web" // nolint: staticcheck + "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/terraform-provider-azurerm/internal/services/appservice/validate" networkValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/network/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" @@ -1161,8 +1162,10 @@ func expandIpRestrictionHeaders(headers []IpRestrictionHeaders) map[string][]str func ExpandCorsSettings(input []CorsSetting) *web.CorsSettings { if len(input) == 0 { + allowedOrigins := make([]string, 0) return &web.CorsSettings{ - AllowedOrigins: &[]string{}, + AllowedOrigins: &allowedOrigins, + SupportCredentials: pointer.To(false), } } var result web.CorsSettings diff --git a/internal/services/appservice/helpers/web_app_schema.go b/internal/services/appservice/helpers/web_app_schema.go deleted file mode 100644 index 5bebaa2b94bc..000000000000 --- a/internal/services/appservice/helpers/web_app_schema.go +++ /dev/null @@ -1,4281 +0,0 @@ -package helpers - -import ( - "fmt" - "strconv" - "strings" - "time" - - "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2021-02-01/web" // nolint: staticcheck - "github.com/Azure/go-autorest/autorest/date" - "github.com/hashicorp/terraform-provider-azurerm/internal/features" - "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" - apimValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/apimanagement/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" -) - -type SiteConfigWindows struct { - AlwaysOn bool `tfschema:"always_on"` - ApiManagementConfigId string `tfschema:"api_management_api_id"` - ApiDefinition string `tfschema:"api_definition_url"` - AppCommandLine string `tfschema:"app_command_line"` - AutoHeal bool `tfschema:"auto_heal_enabled"` - AutoHealSettings []AutoHealSettingWindows `tfschema:"auto_heal_setting"` - UseManagedIdentityACR bool `tfschema:"container_registry_use_managed_identity"` - ContainerRegistryUserMSI string `tfschema:"container_registry_managed_identity_client_id"` - DefaultDocuments []string `tfschema:"default_documents"` - Http2Enabled bool `tfschema:"http2_enabled"` - IpRestriction []IpRestriction `tfschema:"ip_restriction"` - ScmUseMainIpRestriction bool `tfschema:"scm_use_main_ip_restriction"` - ScmIpRestriction []IpRestriction `tfschema:"scm_ip_restriction"` - LoadBalancing string `tfschema:"load_balancing_mode"` - LocalMysql bool `tfschema:"local_mysql_enabled"` - ManagedPipelineMode string `tfschema:"managed_pipeline_mode"` - RemoteDebugging bool `tfschema:"remote_debugging_enabled"` - RemoteDebuggingVersion string `tfschema:"remote_debugging_version"` - ScmType string `tfschema:"scm_type"` - 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"` - WorkerCount int `tfschema:"worker_count"` - ApplicationStack []ApplicationStackWindows `tfschema:"application_stack"` - VirtualApplications []VirtualApplication `tfschema:"virtual_application"` - MinTlsVersion string `tfschema:"minimum_tls_version"` - ScmMinTlsVersion string `tfschema:"scm_minimum_tls_version"` - Cors []CorsSetting `tfschema:"cors"` - DetailedErrorLogging bool `tfschema:"detailed_error_logging_enabled"` - WindowsFxVersion string `tfschema:"windows_fx_version"` - VnetRouteAllEnabled bool `tfschema:"vnet_route_all_enabled"` - // TODO new properties / blocks - // SiteLimits []SiteLimitsSettings `tfschema:"site_limits"` // TODO - ASE related for limiting App resource consumption - // PushSettings - Supported in SDK, but blocked by manual step needed for connecting app to notification hub. -} - -type SiteConfigLinux struct { - AlwaysOn bool `tfschema:"always_on"` - ApiManagementConfigId string `tfschema:"api_management_api_id"` - ApiDefinition string `tfschema:"api_definition_url"` - AppCommandLine string `tfschema:"app_command_line"` - AutoHeal bool `tfschema:"auto_heal_enabled"` - AutoHealSettings []AutoHealSettingLinux `tfschema:"auto_heal_setting"` - UseManagedIdentityACR bool `tfschema:"container_registry_use_managed_identity"` - ContainerRegistryMSI string `tfschema:"container_registry_managed_identity_client_id"` - DefaultDocuments []string `tfschema:"default_documents"` - Http2Enabled bool `tfschema:"http2_enabled"` - IpRestriction []IpRestriction `tfschema:"ip_restriction"` - ScmUseMainIpRestriction bool `tfschema:"scm_use_main_ip_restriction"` - ScmIpRestriction []IpRestriction `tfschema:"scm_ip_restriction"` - LoadBalancing string `tfschema:"load_balancing_mode"` - LocalMysql bool `tfschema:"local_mysql_enabled"` - ManagedPipelineMode string `tfschema:"managed_pipeline_mode"` - RemoteDebugging bool `tfschema:"remote_debugging_enabled"` - RemoteDebuggingVersion string `tfschema:"remote_debugging_version"` - ScmType string `tfschema:"scm_type"` - 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:"worker_count"` - ApplicationStack []ApplicationStackLinux `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_enabled"` - LinuxFxVersion string `tfschema:"linux_fx_version"` - VnetRouteAllEnabled bool `tfschema:"vnet_route_all_enabled"` - // SiteLimits []SiteLimitsSettings `tfschema:"site_limits"` // TODO - New block to (possibly) support? No way to configure this in the portal? -} - -func SiteConfigSchemaWindows() *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, - Default: true, - }, - - "api_management_api_id": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: apimValidate.ApiID, - }, - - "api_definition_url": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.IsURLWithHTTPorHTTPS, - }, - - "application_stack": windowsApplicationStackSchema(), - - "app_command_line": { - Type: pluginsdk.TypeString, - Optional: true, - }, - - "auto_heal_enabled": { - Type: pluginsdk.TypeBool, - Optional: true, - Default: false, - RequiredWith: []string{ - "site_config.0.auto_heal_setting", - }, - }, - - "auto_heal_setting": autoHealSettingSchemaWindows(), - - "container_registry_use_managed_identity": { - Type: pluginsdk.TypeBool, - Optional: true, - Default: false, - }, - - "container_registry_managed_identity_client_id": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.IsUUID, - }, - - "default_documents": { - Type: pluginsdk.TypeList, - Optional: true, - Computed: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - }, - }, - - "http2_enabled": { - Type: pluginsdk.TypeBool, - Optional: true, - Default: false, - }, - - "ip_restriction": IpRestrictionSchema(), - - "scm_use_main_ip_restriction": { - Type: pluginsdk.TypeBool, - Optional: true, - Default: false, - }, - - "scm_ip_restriction": IpRestrictionSchema(), - - "local_mysql_enabled": { - Type: pluginsdk.TypeBool, - Optional: true, - Default: false, - }, - - "load_balancing_mode": { - Type: pluginsdk.TypeString, - Optional: true, - Default: string(web.SiteLoadBalancingLeastRequests), - ValidateFunc: validation.StringInSlice([]string{ - string(web.SiteLoadBalancingLeastRequests), - string(web.SiteLoadBalancingWeightedRoundRobin), - string(web.SiteLoadBalancingLeastResponseTime), - string(web.SiteLoadBalancingWeightedTotalTraffic), - string(web.SiteLoadBalancingRequestHash), - string(web.SiteLoadBalancingPerSiteRoundRobin), - }, false), - }, - - "managed_pipeline_mode": { - Type: pluginsdk.TypeString, - Optional: true, - Default: string(web.ManagedPipelineModeIntegrated), - ValidateFunc: validation.StringInSlice([]string{ - string(web.ManagedPipelineModeClassic), - string(web.ManagedPipelineModeIntegrated), - }, false), - }, - - "remote_debugging_enabled": { - Type: pluginsdk.TypeBool, - Optional: true, - Default: false, - }, - - "remote_debugging_version": { - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice([]string{ - "VS2017", - "VS2019", - }, false), - }, - - "scm_type": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "use_32_bit_worker": { - Type: pluginsdk.TypeBool, - Optional: true, - Default: true, // Variable default value depending on several factors, such as plan type? - }, - - "websockets_enabled": { - Type: pluginsdk.TypeBool, - Optional: true, - Default: 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), - }, - - "health_check_path": { - Type: pluginsdk.TypeString, - Optional: true, - }, - - "health_check_eviction_time_in_min": { - 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`", - }, - - "worker_count": { - Type: pluginsdk.TypeInt, - Optional: true, - Computed: true, - ValidateFunc: validation.IntBetween(1, 100), - }, - - "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), - }, - - "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), - }, - - "cors": CorsSettingsSchema(), - - "virtual_application": virtualApplicationsSchema(), - - "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_enabled": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "linux_fx_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "windows_fx_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - }, - }, - } -} - -func SiteConfigSchemaWindowsComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "always_on": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "api_management_api_id": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "api_definition_url": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "application_stack": windowsApplicationStackSchemaComputed(), - - "app_command_line": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "auto_heal_enabled": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "auto_heal_setting": autoHealSettingSchemaWindowsComputed(), - - "container_registry_use_managed_identity": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "container_registry_managed_identity_client_id": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "default_documents": { - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - }, - }, - - "http2_enabled": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "ip_restriction": IpRestrictionSchemaComputed(), - - "scm_use_main_ip_restriction": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "scm_ip_restriction": IpRestrictionSchemaComputed(), - - "local_mysql_enabled": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "load_balancing_mode": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "managed_pipeline_mode": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "remote_debugging_enabled": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "remote_debugging_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "scm_type": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "use_32_bit_worker": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "websockets_enabled": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "ftps_state": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "health_check_path": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "health_check_eviction_time_in_min": { - Type: pluginsdk.TypeInt, - Computed: true, - }, - - "worker_count": { - Type: pluginsdk.TypeInt, - Computed: true, - }, - - "minimum_tls_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "scm_minimum_tls_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "cors": CorsSettingsSchemaComputed(), - - "virtual_application": virtualApplicationsSchemaComputed(), - - "detailed_error_logging_enabled": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "windows_fx_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "vnet_route_all_enabled": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - }, - }, - } -} - -func SiteConfigSchemaLinux() *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, - Default: true, - }, - - "api_management_api_id": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: apimValidate.ApiID, - }, - - "api_definition_url": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.IsURLWithHTTPorHTTPS, - }, - - "app_command_line": { - Type: pluginsdk.TypeString, - Optional: true, - }, - - "application_stack": linuxApplicationStackSchema(), - - "auto_heal_enabled": { - Type: pluginsdk.TypeBool, - Optional: true, - RequiredWith: []string{ - "site_config.0.auto_heal_setting", - }, - }, - - "auto_heal_setting": autoHealSettingSchemaLinux(), - - "container_registry_use_managed_identity": { - Type: pluginsdk.TypeBool, - Optional: true, - Default: false, - }, - - "container_registry_managed_identity_client_id": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.IsUUID, - }, - - "default_documents": { - Type: pluginsdk.TypeList, - Optional: true, - Computed: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - }, - }, - - "http2_enabled": { - Type: pluginsdk.TypeBool, - Optional: true, - Default: false, - }, - - "ip_restriction": IpRestrictionSchema(), - - "scm_use_main_ip_restriction": { - Type: pluginsdk.TypeBool, - Optional: true, - Default: false, - }, - - "scm_ip_restriction": IpRestrictionSchema(), - - "local_mysql_enabled": { - Type: pluginsdk.TypeBool, - Optional: true, - Default: false, - }, - - "load_balancing_mode": { - Type: pluginsdk.TypeString, - Optional: true, - Default: "LeastRequests", - ValidateFunc: validation.StringInSlice([]string{ - "LeastRequests", // Service default - "WeightedRoundRobin", - "LeastResponseTime", - "WeightedTotalTraffic", - "RequestHash", - "PerSiteRoundRobin", - }, false), - }, - - "managed_pipeline_mode": { - Type: pluginsdk.TypeString, - Optional: true, - Default: string(web.ManagedPipelineModeIntegrated), - ValidateFunc: validation.StringInSlice([]string{ - string(web.ManagedPipelineModeClassic), - string(web.ManagedPipelineModeIntegrated), - }, false), - }, - - "remote_debugging_enabled": { - Type: pluginsdk.TypeBool, - Optional: true, - Default: false, - }, - - "remote_debugging_version": { - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice([]string{ - "VS2017", - "VS2019", - }, false), - }, - - "scm_type": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "use_32_bit_worker": { - Type: pluginsdk.TypeBool, - Optional: true, - Default: true, - }, - - "websockets_enabled": { - Type: pluginsdk.TypeBool, - Optional: true, - Default: 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), - }, - - "health_check_path": { - Type: pluginsdk.TypeString, - Optional: true, - }, - - "health_check_eviction_time_in_min": { - 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`", - }, - - "worker_count": { - Type: pluginsdk.TypeInt, - Optional: true, - Computed: true, - ValidateFunc: validation.IntBetween(1, 100), - }, - - "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), - }, - - "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), - }, - - "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_enabled": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "linux_fx_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - }, - }, - } -} - -func SiteConfigSchemaLinuxComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "always_on": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "api_management_api_id": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "api_definition_url": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "app_command_line": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "application_stack": linuxApplicationStackSchemaComputed(), - - "auto_heal_enabled": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "auto_heal_setting": autoHealSettingSchemaLinuxComputed(), - - "container_registry_use_managed_identity": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "container_registry_managed_identity_client_id": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "default_documents": { - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - }, - }, - - "http2_enabled": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "ip_restriction": IpRestrictionSchemaComputed(), - - "scm_use_main_ip_restriction": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "scm_ip_restriction": IpRestrictionSchemaComputed(), - - "local_mysql_enabled": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "load_balancing_mode": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "managed_pipeline_mode": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "remote_debugging_enabled": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "remote_debugging_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "scm_type": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "use_32_bit_worker": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "websockets_enabled": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "ftps_state": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "health_check_path": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "health_check_eviction_time_in_min": { - Type: pluginsdk.TypeInt, - Computed: true, - }, - - "worker_count": { - Type: pluginsdk.TypeInt, - Computed: true, - }, - - "minimum_tls_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "scm_minimum_tls_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "cors": CorsSettingsSchemaComputed(), - - "detailed_error_logging_enabled": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "linux_fx_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "vnet_route_all_enabled": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - }, - }, - } -} - -type ApplicationStackWindows struct { - NetFrameworkVersion string `tfschema:"dotnet_version"` - PhpVersion string `tfschema:"php_version"` - JavaVersion string `tfschema:"java_version"` - PythonVersion string `tfschema:"python_version"` - NodeVersion string `tfschema:"node_version"` - JavaContainer string `tfschema:"java_container"` - JavaContainerVersion string `tfschema:"java_container_version"` - DockerContainerName string `tfschema:"docker_container_name"` - DockerContainerRegistry string `tfschema:"docker_container_registry"` - DockerContainerTag string `tfschema:"docker_container_tag"` - CurrentStack string `tfschema:"current_stack"` -} - -// Version information for the below validations was taken in part from - https://github.com/Azure/app-service-linux-docs/tree/master/Runtime_Support -// There is no 3.0 version of .netFramework, setting the property to this value causing app to be broke. -func windowsApplicationStackSchema() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "dotnet_version": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: func() pluginsdk.SchemaValidateFunc { - if !features.FourPointOh() { - return validation.StringInSlice([]string{ - "v2.0", - "v3.0", - "core3.1", - "v4.0", - "v5.0", - "v6.0", - "v7.0"}, false) - } - return validation.StringInSlice([]string{ - "v2.0", - "core3.1", - "v4.0", - "v5.0", - "v6.0", - "v7.0", - }, false) - }(), - AtLeastOneOf: []string{ - "site_config.0.application_stack.0.docker_container_name", - "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.php_version", - "site_config.0.application_stack.0.python_version", - }, - }, - - "php_version": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - "7.4", - }, false), - AtLeastOneOf: []string{ - "site_config.0.application_stack.0.docker_container_name", - "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.php_version", - "site_config.0.application_stack.0.python_version", - }, - }, - - "python_version": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - "3.4.0", // Note: The portal only lists `3.6`, however, the service only uses the value `3.4.0`. Anything else is silently discarded - }, false), - AtLeastOneOf: []string{ - "site_config.0.application_stack.0.docker_container_name", - "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.php_version", - "site_config.0.application_stack.0.python_version", - }, - }, - - "node_version": { // Discarded by service if JavaVersion is specified - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - "12-LTS", - "14-LTS", - "16-LTS", - "18-LTS", - }, false), - AtLeastOneOf: []string{ - "site_config.0.application_stack.0.docker_container_name", - "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.php_version", - "site_config.0.application_stack.0.python_version", - }, - ConflictsWith: []string{ - "site_config.0.application_stack.0.java_version", - }, - }, - - "java_version": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - "1.8", - "11", - "17", - }, false), - AtLeastOneOf: []string{ - "site_config.0.application_stack.0.docker_container_name", - "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.php_version", - "site_config.0.application_stack.0.python_version", - }, - }, - - "java_container": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - "JAVA", - "JETTY", - "TOMCAT", - }, false), - RequiredWith: []string{ - "site_config.0.application_stack.0.java_container_version", - }, - }, - - "java_container_version": { - Type: pluginsdk.TypeString, - Optional: true, - RequiredWith: []string{ - "site_config.0.application_stack.0.java_container", - }, - }, - - "docker_container_name": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - AtLeastOneOf: []string{ - "site_config.0.application_stack.0.docker_container_name", - "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.php_version", - "site_config.0.application_stack.0.python_version", - }, - RequiredWith: []string{ - "site_config.0.application_stack.0.docker_container_tag", - }, - }, - - "docker_container_registry": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "docker_container_tag": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - RequiredWith: []string{ - "site_config.0.application_stack.0.docker_container_name", - }, - }, - - "current_stack": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - "dotnet", - "dotnetcore", - "node", - "python", - "php", - "java", - }, false), - }, - }, - }, - } -} - -func windowsApplicationStackSchemaComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "dotnet_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "php_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "python_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "node_version": { // Discarded by service if JavaVersion is specified - Type: pluginsdk.TypeString, - Computed: true, - }, - - "java_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "java_container": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "java_container_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "docker_container_name": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "docker_container_registry": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "docker_container_tag": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "current_stack": { - Type: pluginsdk.TypeString, - Computed: true, - }, - }, - }, - } -} - -type ApplicationStackLinux struct { - NetFrameworkVersion string `tfschema:"dotnet_version"` - PhpVersion string `tfschema:"php_version"` - PythonVersion string `tfschema:"python_version"` // Linux Only? - NodeVersion string `tfschema:"node_version"` - JavaVersion string `tfschema:"java_version"` - JavaServer string `tfschema:"java_server"` - JavaServerVersion string `tfschema:"java_server_version"` - DockerImageTag string `tfschema:"docker_image_tag"` - DockerImage string `tfschema:"docker_image"` - RubyVersion string `tfschema:"ruby_version"` -} - -// version information in the validation here was taken mostly from - `az webapp list-runtimes --linux` -func linuxApplicationStackSchema() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "dotnet_version": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - "3.1", - "5.0", - "6.0", - "7.0", - }, false), - AtLeastOneOf: []string{ - "site_config.0.application_stack.0.docker_image", - "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.php_version", - "site_config.0.application_stack.0.python_version", - "site_config.0.application_stack.0.ruby_version", - }, - ConflictsWith: []string{ - "site_config.0.application_stack.0.php_version", - "site_config.0.application_stack.0.python_version", - "site_config.0.application_stack.0.node_version", - "site_config.0.application_stack.0.ruby_version", - "site_config.0.application_stack.0.java_version", - }, - }, - - "php_version": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - "7.4", - "8.0", - "8.1", - }, false), - AtLeastOneOf: []string{ - "site_config.0.application_stack.0.docker_image", - "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.php_version", - "site_config.0.application_stack.0.python_version", - "site_config.0.application_stack.0.ruby_version", - }, - ConflictsWith: []string{ - "site_config.0.application_stack.0.dotnet_version", - "site_config.0.application_stack.0.python_version", - "site_config.0.application_stack.0.node_version", - "site_config.0.application_stack.0.ruby_version", - "site_config.0.application_stack.0.java_version", - }, - }, - - "python_version": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - "3.7", - "3.8", - "3.9", - "3.10", - }, false), - AtLeastOneOf: []string{ - "site_config.0.application_stack.0.docker_image", - "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.php_version", - "site_config.0.application_stack.0.python_version", - "site_config.0.application_stack.0.ruby_version", - }, - ConflictsWith: []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.php_version", - "site_config.0.application_stack.0.ruby_version", - }, - }, - - "node_version": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - "12-lts", - "14-lts", - "16-lts", - "18-lts", - }, false), - AtLeastOneOf: []string{ - "site_config.0.application_stack.0.docker_image", - "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.php_version", - "site_config.0.application_stack.0.python_version", - "site_config.0.application_stack.0.ruby_version", - }, - ConflictsWith: []string{ - "site_config.0.application_stack.0.dotnet_version", - "site_config.0.application_stack.0.php_version", - "site_config.0.application_stack.0.python_version", - "site_config.0.application_stack.0.ruby_version", - "site_config.0.application_stack.0.java_version", - }, - }, - - "ruby_version": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - "2.6", - "2.7", - }, false), - AtLeastOneOf: []string{ - "site_config.0.application_stack.0.docker_image", - "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.php_version", - "site_config.0.application_stack.0.python_version", - "site_config.0.application_stack.0.ruby_version", - }, - ConflictsWith: []string{ - "site_config.0.application_stack.0.dotnet_version", - "site_config.0.application_stack.0.php_version", - "site_config.0.application_stack.0.python_version", - "site_config.0.application_stack.0.node_version", - "site_config.0.application_stack.0.java_version", - }, - }, - - "java_version": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, // There a significant number of variables here, and the versions are not uniformly formatted. - // TODO - Needs notes in the docs for this to help users navigate the inconsistencies in the service. e.g. jre8 va java8 etc - AtLeastOneOf: []string{ - "site_config.0.application_stack.0.docker_image", - "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.php_version", - "site_config.0.application_stack.0.python_version", - "site_config.0.application_stack.0.ruby_version", - }, - ConflictsWith: []string{ - "site_config.0.application_stack.0.dotnet_version", - "site_config.0.application_stack.0.php_version", - "site_config.0.application_stack.0.python_version", - "site_config.0.application_stack.0.node_version", - "site_config.0.application_stack.0.ruby_version", - }, - }, - - "java_server": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - "JAVA", - "TOMCAT", - "JBOSSEAP", - }, false), - }, - - "java_server_version": { - Type: pluginsdk.TypeString, - Optional: true, - }, - - "docker_image": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - AtLeastOneOf: []string{ - "site_config.0.application_stack.0.docker_image", - "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.php_version", - "site_config.0.application_stack.0.python_version", - "site_config.0.application_stack.0.ruby_version", - }, - RequiredWith: []string{ - "site_config.0.application_stack.0.docker_image_tag", - }, - }, - - "docker_image_tag": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - RequiredWith: []string{ - "site_config.0.application_stack.0.docker_image", - }, - }, - }, - }, - } -} - -func linuxApplicationStackSchemaComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "dotnet_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "php_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "python_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "node_version": { // Discarded by service if JavaVersion is specified - Type: pluginsdk.TypeString, - Computed: true, - }, - - "ruby_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "java_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "java_server": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "java_server_version": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "docker_image": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "docker_image_tag": { - Type: pluginsdk.TypeString, - Computed: true, - }, - }, - }, - } -} - -type AutoHealSettingWindows struct { - Triggers []AutoHealTriggerWindows `tfschema:"trigger"` - Actions []AutoHealActionWindows `tfschema:"action"` -} - -type AutoHealSettingLinux struct { - Triggers []AutoHealTriggerLinux `tfschema:"trigger"` - Actions []AutoHealActionLinux `tfschema:"action"` -} - -type AutoHealTriggerWindows struct { - Requests []AutoHealRequestTrigger `tfschema:"requests"` - PrivateMemoryKB int `tfschema:"private_memory_kb"` // Private should be > 102400 KB (100 MB) to 13631488 KB (13 GB), defaults to 0 however and is always present. - StatusCodes []AutoHealStatusCodeTrigger `tfschema:"status_code"` // 0 or more, ranges split by `-`, ranges cannot use sub-status or win32 code - SlowRequests []AutoHealSlowRequest `tfschema:"slow_request"` -} - -type AutoHealTriggerLinux struct { - Requests []AutoHealRequestTrigger `tfschema:"requests"` - StatusCodes []AutoHealStatusCodeTrigger `tfschema:"status_code"` // 0 or more, ranges split by `-`, ranges cannot use sub-status or win32 code - SlowRequests []AutoHealSlowRequest `tfschema:"slow_request"` -} - -type AutoHealRequestTrigger struct { - Count int `tfschema:"count"` - Interval string `tfschema:"interval"` -} - -type AutoHealStatusCodeTrigger struct { - StatusCodeRange string `tfschema:"status_code_range"` // Conflicts with `StatusCode`, `Win32Code`, and `SubStatus` when not a single value... - SubStatus int `tfschema:"sub_status"` - Win32Status string `tfschema:"win32_status"` - Path string `tfschema:"path"` - Count int `tfschema:"count"` - Interval string `tfschema:"interval"` // Format - hh:mm:ss -} - -type AutoHealSlowRequest struct { - TimeTaken string `tfschema:"time_taken"` - Interval string `tfschema:"interval"` - Count int `tfschema:"count"` - Path string `tfschema:"path"` -} - -type AutoHealActionWindows struct { - ActionType string `tfschema:"action_type"` // Enum - CustomAction []AutoHealCustomAction `tfschema:"custom_action"` // Max: 1, needs `action_type` to be "Custom" - MinimumProcessTime string `tfschema:"minimum_process_execution_time"` // Minimum uptime for process before action will trigger -} - -type AutoHealActionLinux struct { - ActionType string `tfschema:"action_type"` // Enum - Only `Recycle` allowed - MinimumProcessTime string `tfschema:"minimum_process_execution_time"` // Minimum uptime for process before action will trigger -} - -type AutoHealCustomAction struct { - Executable string `tfschema:"executable"` - Parameters string `tfschema:"parameters"` -} - -func autoHealSettingSchemaWindows() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Optional: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "trigger": autoHealTriggerSchemaWindows(), - - "action": autoHealActionSchemaWindows(), - }, - }, - RequiredWith: []string{ - "site_config.0.auto_heal_enabled", - }, - } -} - -func autoHealSettingSchemaWindowsComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "trigger": autoHealTriggerSchemaWindowsComputed(), - - "action": autoHealActionSchemaWindowsComputed(), - }, - }, - } -} - -func autoHealSettingSchemaLinux() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Optional: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "trigger": autoHealTriggerSchemaLinux(), - - "action": autoHealActionSchemaLinux(), - }, - }, - RequiredWith: []string{ - "site_config.0.auto_heal_enabled", - }, - } -} - -func autoHealSettingSchemaLinuxComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "trigger": autoHealTriggerSchemaLinuxComputed(), - - "action": autoHealActionSchemaLinuxComputed(), - }, - }, - } -} - -func autoHealActionSchemaWindows() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Required: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "action_type": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.AutoHealActionTypeCustomAction), - string(web.AutoHealActionTypeLogEvent), - string(web.AutoHealActionTypeRecycle), - }, false), - }, - - "custom_action": { - Type: pluginsdk.TypeList, - Optional: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "executable": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "parameters": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - }, - }, - }, - - "minimum_process_execution_time": { - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - // ValidateFunc: // TODO - Time in hh:mm:ss, because why not... - }, - }, - }, - } -} - -func autoHealActionSchemaWindowsComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "action_type": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "custom_action": { - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "executable": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "parameters": { - Type: pluginsdk.TypeString, - Computed: true, - }, - }, - }, - }, - - "minimum_process_execution_time": { - Type: pluginsdk.TypeString, - Computed: true, - }, - }, - }, - } -} - -func autoHealActionSchemaLinux() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Optional: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "action_type": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.AutoHealActionTypeRecycle), - }, false), - }, - - "minimum_process_execution_time": { - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - // ValidateFunc: // TODO - Time in hh:mm:ss, because why not... - }, - }, - }, - } -} - -func autoHealActionSchemaLinuxComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "action_type": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "minimum_process_execution_time": { - Type: pluginsdk.TypeString, - Computed: true, - }, - }, - }, - } -} - -// (@jackofallops) - trigger schemas intentionally left long-hand for now -func autoHealTriggerSchemaWindows() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Required: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "requests": { - Type: pluginsdk.TypeList, - Optional: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "count": { - Type: pluginsdk.TypeInt, - Required: true, - ValidateFunc: validation.IntAtLeast(1), - }, - - "interval": { - Type: pluginsdk.TypeString, - Required: true, - // ValidateFunc: validation.IsRFC3339Time, // TODO should be hh:mm:ss - This is too loose, need to improve - }, - }, - }, - }, - - "private_memory_kb": { - Type: pluginsdk.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(102400, 13631488), - }, - - "status_code": { - Type: pluginsdk.TypeList, - Optional: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "status_code_range": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: nil, // TODO - status code range validation - }, - - "count": { - Type: pluginsdk.TypeInt, - Required: true, - ValidateFunc: validation.IntAtLeast(1), - }, - - "interval": { - Type: pluginsdk.TypeString, - Required: true, - // ValidateFunc: validation.IsRFC3339Time, - }, - - "sub_status": { - Type: pluginsdk.TypeInt, - Optional: true, - ValidateFunc: nil, // TODO - no docs on this, needs investigation - }, - - "win32_status": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: nil, // TODO - no docs on this, needs investigation - }, - - "path": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - }, - }, - }, - - "slow_request": { - Type: pluginsdk.TypeList, - Optional: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "time_taken": { - Type: pluginsdk.TypeString, - Required: true, - // ValidateFunc: validation.IsRFC3339Time, - }, - - "interval": { - Type: pluginsdk.TypeString, - Required: true, - // ValidateFunc: validation.IsRFC3339Time, - }, - - "count": { - Type: pluginsdk.TypeInt, - Required: true, - ValidateFunc: validation.IntAtLeast(1), - }, - - "path": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - }, - }, - }, - }, - }, - } -} - -func autoHealTriggerSchemaWindowsComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "requests": { - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "count": { - Type: pluginsdk.TypeInt, - Computed: true, - }, - - "interval": { - Type: pluginsdk.TypeString, - Computed: true, - }, - }, - }, - }, - - "private_memory_kb": { - Type: pluginsdk.TypeInt, - Computed: true, - }, - - "status_code": { - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "status_code_range": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "count": { - Type: pluginsdk.TypeInt, - Computed: true, - }, - - "interval": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "sub_status": { - Type: pluginsdk.TypeInt, - Computed: true, - }, - - "win32_status": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "path": { - Type: pluginsdk.TypeString, - Computed: true, - }, - }, - }, - }, - - "slow_request": { - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "time_taken": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "interval": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "count": { - Type: pluginsdk.TypeInt, - Computed: true, - }, - - "path": { - Type: pluginsdk.TypeString, - Computed: true, - }, - }, - }, - }, - }, - }, - } -} - -// (@jackofallops) - trigger schemas intentionally left long-hand for now -func autoHealTriggerSchemaLinux() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Optional: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "requests": { - Type: pluginsdk.TypeList, - Optional: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "count": { - Type: pluginsdk.TypeInt, - Required: true, - ValidateFunc: validation.IntAtLeast(1), - }, - - "interval": { - Type: pluginsdk.TypeString, - Required: true, - // ValidateFunc: validation.IsRFC3339Time, // TODO should be hh:mm:ss - This is too loose, need to improve? - }, - }, - }, - }, - - "status_code": { - Type: pluginsdk.TypeList, - Optional: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "status_code_range": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: nil, // TODO - status code range validation - }, - - "count": { - Type: pluginsdk.TypeInt, - Required: true, - ValidateFunc: validation.IntAtLeast(1), - }, - - "interval": { - Type: pluginsdk.TypeString, - Required: true, - // ValidateFunc: validation.IsRFC3339Time, - }, - - "sub_status": { - Type: pluginsdk.TypeInt, - Optional: true, - ValidateFunc: nil, // TODO - no docs on this, needs investigation - }, - - "win32_status": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: nil, // TODO - no docs on this, needs investigation - }, - - "path": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - }, - }, - }, - - "slow_request": { - Type: pluginsdk.TypeList, - Optional: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "time_taken": { - Type: pluginsdk.TypeString, - Required: true, - // ValidateFunc: validation.IsRFC3339Time, - }, - - "interval": { - Type: pluginsdk.TypeString, - Required: true, - // ValidateFunc: validation.IsRFC3339Time, - }, - - "count": { - Type: pluginsdk.TypeInt, - Required: true, - ValidateFunc: validation.IntAtLeast(1), - }, - - "path": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - }, - }, - }, - }, - }, - } -} - -func autoHealTriggerSchemaLinuxComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "requests": { - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "count": { - Type: pluginsdk.TypeInt, - Computed: true, - }, - - "interval": { - Type: pluginsdk.TypeString, - Computed: true, - }, - }, - }, - }, - - "status_code": { - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "status_code_range": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "count": { - Type: pluginsdk.TypeInt, - Computed: true, - }, - - "interval": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "sub_status": { - Type: pluginsdk.TypeInt, - Computed: true, - }, - - "win32_status": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "path": { - Type: pluginsdk.TypeString, - Computed: true, - }, - }, - }, - }, - - "slow_request": { - Type: pluginsdk.TypeList, - Optional: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "time_taken": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "interval": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "count": { - Type: pluginsdk.TypeInt, - Computed: true, - }, - - "path": { - Type: pluginsdk.TypeString, - Computed: true, - }, - }, - }, - }, - }, - }, - } -} - -type VirtualApplication struct { - VirtualPath string `tfschema:"virtual_path"` - PhysicalPath string `tfschema:"physical_path"` - Preload bool `tfschema:"preload"` - VirtualDirectories []VirtualDirectory `tfschema:"virtual_directory"` -} - -type VirtualDirectory struct { - VirtualPath string `tfschema:"virtual_path"` - PhysicalPath string `tfschema:"physical_path"` -} - -func virtualApplicationsSchema() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeSet, - Optional: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "virtual_path": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "physical_path": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "preload": { - Type: pluginsdk.TypeBool, - Required: true, - }, - - "virtual_directory": { - Type: pluginsdk.TypeSet, - Optional: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "virtual_path": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "physical_path": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - }, - }, - }, - }, - }, - } -} - -func virtualApplicationsSchemaComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "virtual_path": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "physical_path": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "preload": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "virtual_directory": { - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "virtual_path": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "physical_path": { - Type: pluginsdk.TypeString, - Computed: true, - }, - }, - }, - }, - }, - }, - } -} - -type StorageAccount struct { - Name string `tfschema:"name"` - Type string `tfschema:"type"` - AccountName string `tfschema:"account_name"` - ShareName string `tfschema:"share_name"` - AccessKey string `tfschema:"access_key"` - MountPath string `tfschema:"mount_path"` -} - -func StorageAccountSchema() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeSet, - Optional: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "name": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "type": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.AzureStorageTypeAzureBlob), - string(web.AzureStorageTypeAzureFiles), - }, false), - }, - - "account_name": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "share_name": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "access_key": { - Type: pluginsdk.TypeString, - Required: true, - Sensitive: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "mount_path": { - Type: pluginsdk.TypeString, - Optional: true, - }, - }, - }, - } -} - -func StorageAccountSchemaWindows() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeSet, - Optional: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "name": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "type": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.AzureStorageTypeAzureFiles), - }, false), - }, - - "account_name": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "share_name": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "access_key": { - Type: pluginsdk.TypeString, - Required: true, - Sensitive: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "mount_path": { - Type: pluginsdk.TypeString, - Optional: true, - }, - }, - }, - } -} - -func StorageAccountSchemaComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "name": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "type": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "account_name": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "share_name": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "access_key": { - Type: pluginsdk.TypeString, - Computed: true, - Sensitive: true, - }, - - "mount_path": { - Type: pluginsdk.TypeString, - Computed: true, - }, - }, - }, - } -} - -type Backup struct { - Name string `tfschema:"name"` - StorageAccountUrl string `tfschema:"storage_account_url"` - Enabled bool `tfschema:"enabled"` - Schedule []BackupSchedule `tfschema:"schedule"` -} - -type BackupSchedule struct { - FrequencyInterval int `tfschema:"frequency_interval"` - FrequencyUnit string `tfschema:"frequency_unit"` - KeepAtLeastOneBackup bool `tfschema:"keep_at_least_one_backup"` - RetentionPeriodDays int `tfschema:"retention_period_days"` - StartTime string `tfschema:"start_time"` - LastExecutionTime string `tfschema:"last_execution_time"` -} - -func BackupSchema() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Optional: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "name": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - Description: "The name which should be used for this Backup.", - }, - - "storage_account_url": { - Type: pluginsdk.TypeString, - Required: true, - Sensitive: true, - ValidateFunc: validation.IsURLWithHTTPS, - Description: "The SAS URL to the container.", - }, - - "enabled": { - Type: pluginsdk.TypeBool, - Optional: true, - Default: true, - Description: "Should this backup job be enabled?", - }, - - "schedule": { - Type: pluginsdk.TypeList, - Required: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "frequency_interval": { - Type: pluginsdk.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(0, 1000), - Description: "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`).", - }, - - "frequency_unit": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - "Day", - "Hour", - }, false), - Description: "The unit of time for how often the backup should take place. Possible values include: `Day` and `Hour`.", - }, - - "keep_at_least_one_backup": { - Type: pluginsdk.TypeBool, - Optional: true, - Default: false, - Description: "Should the service keep at least one backup, regardless of age of backup. Defaults to `false`.", - }, - - "retention_period_days": { - Type: pluginsdk.TypeInt, - Optional: true, - Default: 30, - ValidateFunc: validation.IntBetween(0, 9999999), - Description: "After how many days backups should be deleted.", - }, - - "start_time": { - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - Description: "When the schedule should start working in RFC-3339 format.", - // DiffSuppressFunc: suppress.RFC3339Time, - // ValidateFunc: validation.IsRFC3339Time, - }, - - "last_execution_time": { - Type: pluginsdk.TypeString, - Computed: true, - Description: "The time the backup was last attempted.", - }, - }, - }, - }, - }, - }, - } -} - -func BackupSchemaComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "name": { - Type: pluginsdk.TypeString, - Computed: true, - Description: "The name of this Backup.", - }, - - "storage_account_url": { - Type: pluginsdk.TypeString, - Computed: true, - Sensitive: true, - Description: "The SAS URL to the container.", - }, - - "enabled": { - Type: pluginsdk.TypeBool, - Computed: true, - Description: "Is this backup job enabled?", - }, - - "schedule": { - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "frequency_interval": { - Type: pluginsdk.TypeInt, - Computed: true, - Description: "How often the backup should is executed in multiples of the `frequency_unit`.", - }, - - "frequency_unit": { - Type: pluginsdk.TypeString, - Computed: true, - Description: "The unit of time for how often the backup takes place.", - }, - - "keep_at_least_one_backup": { - Type: pluginsdk.TypeBool, - Computed: true, - Description: "Does the service keep at least one backup, regardless of age of backup.", - }, - - "retention_period_days": { - Type: pluginsdk.TypeInt, - Computed: true, - Description: "After how many days are backups deleted.", - }, - - "start_time": { - Type: pluginsdk.TypeString, - Computed: true, - Description: "When the schedule should start working in RFC-3339 format.", - }, - - "last_execution_time": { - Type: pluginsdk.TypeString, - Computed: true, - Description: "The time the backup was last attempted.", - }, - }, - }, - }, - }, - }, - } -} - -type ConnectionString struct { - Name string `tfschema:"name"` - Type string `tfschema:"type"` - Value string `tfschema:"value"` -} - -func ConnectionStringSchema() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeSet, - Optional: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "name": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - Description: "The name which should be used for this Connection.", - }, - - "type": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.ConnectionStringTypeAPIHub), - string(web.ConnectionStringTypeCustom), - string(web.ConnectionStringTypeDocDb), - string(web.ConnectionStringTypeEventHub), - string(web.ConnectionStringTypeMySQL), - string(web.ConnectionStringTypeNotificationHub), - string(web.ConnectionStringTypePostgreSQL), - string(web.ConnectionStringTypeRedisCache), - string(web.ConnectionStringTypeServiceBus), - string(web.ConnectionStringTypeSQLAzure), - string(web.ConnectionStringTypeSQLServer), - }, false), - Description: "Type of database. Possible values include: `MySQL`, `SQLServer`, `SQLAzure`, `Custom`, `NotificationHub`, `ServiceBus`, `EventHub`, `APIHub`, `DocDb`, `RedisCache`, and `PostgreSQL`.", - }, - - "value": { - Type: pluginsdk.TypeString, - Required: true, - Sensitive: true, - Description: "The connection string value.", - }, - }, - }, - } -} - -func ConnectionStringSchemaComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeSet, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "name": { - Type: pluginsdk.TypeString, - Computed: true, - Description: "The name of this Connection.", - }, - - "type": { - Type: pluginsdk.TypeString, - Computed: true, - Description: "The type of database.", - }, - - "value": { - Type: pluginsdk.TypeString, - Computed: true, - Sensitive: true, - Description: "The connection string value.", - }, - }, - }, - } -} - -type LogsConfig struct { - ApplicationLogs []ApplicationLog `tfschema:"application_logs"` - HttpLogs []HttpLog `tfschema:"http_logs"` - DetailedErrorMessages bool `tfschema:"detailed_error_messages"` - FailedRequestTracing bool `tfschema:"failed_request_tracing"` -} - -type ApplicationLog struct { - FileSystemLevel string `tfschema:"file_system_level"` - AzureBlobStorage []AzureBlobStorage `tfschema:"azure_blob_storage"` -} - -type AzureBlobStorage struct { - Level string `tfschema:"level"` - SasUrl string `tfschema:"sas_url"` - RetentionInDays int `tfschema:"retention_in_days"` -} - -type HttpLog struct { - FileSystems []LogsFileSystem `tfschema:"file_system"` - AzureBlobStorage []AzureBlobStorageHttp `tfschema:"azure_blob_storage"` -} - -type AzureBlobStorageHttp struct { - SasUrl string `tfschema:"sas_url"` - RetentionInDays int `tfschema:"retention_in_days"` -} - -type LogsFileSystem struct { - RetentionMB int `tfschema:"retention_in_mb"` - RetentionDays int `tfschema:"retention_in_days"` -} - -func LogsConfigSchema() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Optional: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "application_logs": applicationLogSchema(), - - "http_logs": httpLogSchema(), - - "failed_request_tracing": { - Type: pluginsdk.TypeBool, - Optional: true, - Default: false, - }, - - "detailed_error_messages": { - Type: pluginsdk.TypeBool, - Optional: true, - Default: false, - }, - }, - }, - } -} - -func LogsConfigSchemaComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "application_logs": applicationLogSchemaComputed(), - - "http_logs": httpLogSchemaComputed(), - - "failed_request_tracing": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - - "detailed_error_messages": { - Type: pluginsdk.TypeBool, - Computed: true, - }, - }, - }, - } -} - -func applicationLogSchema() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Optional: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "file_system_level": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.LogLevelError), - string(web.LogLevelInformation), - string(web.LogLevelVerbose), - string(web.LogLevelWarning), - }, false), - }, - - "azure_blob_storage": appLogBlobStorageSchema(), - }, - }, - } -} - -func applicationLogSchemaComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "file_system_level": { - Type: pluginsdk.TypeString, - Computed: true, - }, - - "azure_blob_storage": appLogBlobStorageSchemaComputed(), - }, - }, - } -} - -func appLogBlobStorageSchema() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Optional: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "level": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.LogLevelError), - string(web.LogLevelInformation), - string(web.LogLevelVerbose), - string(web.LogLevelWarning), - }, false), - }, - "sas_url": { - Type: pluginsdk.TypeString, - Required: true, - }, - "retention_in_days": { - Type: pluginsdk.TypeInt, - Required: true, - // TODO: Validation here? - }, - }, - }, - } -} - -func appLogBlobStorageSchemaComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "level": { - Type: pluginsdk.TypeString, - Computed: true, - }, - "sas_url": { - Type: pluginsdk.TypeString, - Computed: true, - Sensitive: true, - }, - "retention_in_days": { - Type: pluginsdk.TypeInt, - Computed: true, - }, - }, - }, - } -} - -func httpLogSchema() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Optional: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "file_system": httpLogFileSystemSchema(), - - "azure_blob_storage": httpLogBlobStorageSchema(), - }, - }, - } -} - -func httpLogSchemaComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "file_system": httpLogFileSystemSchemaComputed(), - - "azure_blob_storage": httpLogBlobStorageSchemaComputed(), - }, - }, - } -} - -func httpLogFileSystemSchema() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Optional: true, - MaxItems: 1, - ConflictsWith: []string{"logs.0.http_logs.0.azure_blob_storage"}, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "retention_in_mb": { - Type: pluginsdk.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(25, 100), - }, - - "retention_in_days": { - Type: pluginsdk.TypeInt, - Required: true, - ValidateFunc: validation.IntAtLeast(0), - }, - }, - }, - } -} - -func httpLogFileSystemSchemaComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "retention_in_mb": { - Type: pluginsdk.TypeInt, - Computed: true, - }, - - "retention_in_days": { - Type: pluginsdk.TypeInt, - Computed: true, - }, - }, - }, - } -} - -func httpLogBlobStorageSchema() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Optional: true, - MaxItems: 1, - ConflictsWith: []string{"logs.0.http_logs.0.file_system"}, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "sas_url": { - Type: pluginsdk.TypeString, - Required: true, - Sensitive: true, - }, - "retention_in_days": { - Type: pluginsdk.TypeInt, - Optional: true, - Default: 0, - ValidateFunc: validation.IntAtLeast(0), // Variable validation here based on the Service Plan SKU - }, - }, - }, - } -} - -func httpLogBlobStorageSchemaComputed() *pluginsdk.Schema { - return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "sas_url": { - Type: pluginsdk.TypeString, - Computed: true, - Sensitive: true, - }, - "retention_in_days": { - Type: pluginsdk.TypeInt, - Computed: true, - }, - }, - }, - } -} - -func ExpandSiteConfigWindows(siteConfig []SiteConfigWindows, existing *web.SiteConfig, metadata sdk.ResourceMetaData, servicePlan web.AppServicePlan) (*web.SiteConfig, *string, error) { - if len(siteConfig) == 0 { - return nil, nil, nil - } - - expanded := &web.SiteConfig{} - if existing != nil { - expanded = existing - } - - winSiteConfig := siteConfig[0] - - currentStack := "" - if len(winSiteConfig.ApplicationStack) == 1 { - winAppStack := winSiteConfig.ApplicationStack[0] - currentStack = winAppStack.CurrentStack - } - - if servicePlan.Sku != nil && servicePlan.Sku.Name != nil { - if isFreeOrSharedServicePlan(*servicePlan.Sku.Name) { - if winSiteConfig.AlwaysOn { - return nil, nil, fmt.Errorf("always_on cannot be set to true when using Free, F1, D1 Sku") - } - if expanded.AlwaysOn != nil && *expanded.AlwaysOn { - return nil, nil, fmt.Errorf("always_on feature has to be turned off before switching to a free/shared Sku") - } - } - } - expanded.AlwaysOn = utils.Bool(winSiteConfig.AlwaysOn) - - if metadata.ResourceData.HasChange("site_config.0.api_management_api_id") { - expanded.APIManagementConfig = &web.APIManagementConfig{ - ID: utils.String(winSiteConfig.ApiManagementConfigId), - } - } - - if metadata.ResourceData.HasChange("site_config.0.api_definition_url") { - expanded.APIDefinition = &web.APIDefinitionInfo{ - URL: utils.String(winSiteConfig.ApiDefinition), - } - } - - if metadata.ResourceData.HasChange("site_config.0.app_command_line") { - expanded.AppCommandLine = utils.String(winSiteConfig.AppCommandLine) - } - - if metadata.ResourceData.HasChange("site_config.0.application_stack") { - if len(winSiteConfig.ApplicationStack) == 1 { - winAppStack := winSiteConfig.ApplicationStack[0] - expanded.NetFrameworkVersion = utils.String(winAppStack.NetFrameworkVersion) - if winAppStack.CurrentStack == "dotnetcore" { - expanded.NetFrameworkVersion = nil - } - expanded.PhpVersion = utils.String(winAppStack.PhpVersion) - expanded.NodeVersion = utils.String(winAppStack.NodeVersion) - expanded.PythonVersion = utils.String(winAppStack.PythonVersion) - expanded.JavaVersion = utils.String(winAppStack.JavaVersion) - expanded.JavaContainer = utils.String(winAppStack.JavaContainer) - expanded.JavaContainerVersion = utils.String(winAppStack.JavaContainerVersion) - if winAppStack.DockerContainerName != "" { - if winAppStack.DockerContainerRegistry != "" { - expanded.WindowsFxVersion = utils.String(fmt.Sprintf("DOCKER|%s/%s:%s", winAppStack.DockerContainerRegistry, winAppStack.DockerContainerName, winAppStack.DockerContainerTag)) - } else { - expanded.WindowsFxVersion = utils.String(fmt.Sprintf("DOCKER|%s:%s", winAppStack.DockerContainerName, winAppStack.DockerContainerTag)) - } - } - currentStack = winAppStack.CurrentStack - } else { - expanded.WindowsFxVersion = utils.String("") - } - } - - if metadata.ResourceData.HasChange("site_config.0.virtual_application") { - expanded.VirtualApplications = expandVirtualApplicationsForUpdate(winSiteConfig.VirtualApplications) - } else { - expanded.VirtualApplications = expandVirtualApplications(winSiteConfig.VirtualApplications) - } - - expanded.AcrUseManagedIdentityCreds = utils.Bool(winSiteConfig.UseManagedIdentityACR) - - if metadata.ResourceData.HasChange("site_config.0.container_registry_managed_identity_client_id") { - expanded.AcrUserManagedIdentityID = utils.String(winSiteConfig.ContainerRegistryUserMSI) - } - - if metadata.ResourceData.HasChange("site_config.0.default_documents") { - expanded.DefaultDocuments = &winSiteConfig.DefaultDocuments - } - - expanded.HTTP20Enabled = utils.Bool(winSiteConfig.Http2Enabled) - - if metadata.ResourceData.HasChange("site_config.0.ip_restriction") { - ipRestrictions, err := ExpandIpRestrictions(winSiteConfig.IpRestriction) - if err != nil { - return nil, nil, err - } - expanded.IPSecurityRestrictions = ipRestrictions - } - - expanded.ScmIPSecurityRestrictionsUseMain = utils.Bool(winSiteConfig.ScmUseMainIpRestriction) - - if metadata.ResourceData.HasChange("site_config.0.scm_ip_restriction") { - scmIpRestrictions, err := ExpandIpRestrictions(winSiteConfig.ScmIpRestriction) - if err != nil { - return nil, nil, err - } - expanded.ScmIPSecurityRestrictions = scmIpRestrictions - } - - expanded.LocalMySQLEnabled = utils.Bool(winSiteConfig.LocalMysql) - - if metadata.ResourceData.HasChange("site_config.0.load_balancing_mode") { - expanded.LoadBalancing = web.SiteLoadBalancing(winSiteConfig.LoadBalancing) - } - - if metadata.ResourceData.HasChange("site_config.0.managed_pipeline_mode") { - expanded.ManagedPipelineMode = web.ManagedPipelineMode(winSiteConfig.ManagedPipelineMode) - } - - expanded.RemoteDebuggingEnabled = utils.Bool(winSiteConfig.RemoteDebugging) - - if metadata.ResourceData.HasChange("site_config.0.remote_debugging_version") { - expanded.RemoteDebuggingVersion = utils.String(winSiteConfig.RemoteDebuggingVersion) - } - - expanded.Use32BitWorkerProcess = utils.Bool(winSiteConfig.Use32BitWorker) - - expanded.WebSocketsEnabled = utils.Bool(winSiteConfig.WebSockets) - - if metadata.ResourceData.HasChange("site_config.0.ftps_state") { - expanded.FtpsState = web.FtpsState(winSiteConfig.FtpsState) - } - - if metadata.ResourceData.HasChange("site_config.0.health_check_path") { - expanded.HealthCheckPath = utils.String(winSiteConfig.HealthCheckPath) - } - - if metadata.ResourceData.HasChange("site_config.0.worker_count") { - expanded.NumberOfWorkers = utils.Int32(int32(winSiteConfig.WorkerCount)) - } - - if metadata.ResourceData.HasChange("site_config.0.minimum_tls_version") { - expanded.MinTLSVersion = web.SupportedTLSVersions(winSiteConfig.MinTlsVersion) - } - - if metadata.ResourceData.HasChange("site_config.0.scm_minimum_tls_version") { - expanded.ScmMinTLSVersion = web.SupportedTLSVersions(winSiteConfig.ScmMinTlsVersion) - } - - if metadata.ResourceData.HasChange("site_config.0.cors") { - cors := ExpandCorsSettings(winSiteConfig.Cors) - if cors == nil { - cors = &web.CorsSettings{ - AllowedOrigins: &[]string{}, - } - } - expanded.Cors = cors - } - - if metadata.ResourceData.HasChange("site_config.0.auto_heal_enabled") { - expanded.AutoHealEnabled = utils.Bool(winSiteConfig.AutoHeal) - } - - if metadata.ResourceData.HasChange("site_config.0.auto_heal_setting") { - expanded.AutoHealRules = expandAutoHealSettingsWindows(winSiteConfig.AutoHealSettings) - } - - if metadata.ResourceData.HasChange("site_config.0.vnet_route_all_enabled") { - expanded.VnetRouteAllEnabled = utils.Bool(winSiteConfig.VnetRouteAllEnabled) - } - - return expanded, ¤tStack, nil -} - -func ExpandSiteConfigLinux(siteConfig []SiteConfigLinux, existing *web.SiteConfig, metadata sdk.ResourceMetaData, servicePlan web.AppServicePlan) (*web.SiteConfig, error) { - if len(siteConfig) == 0 { - return nil, nil - } - expanded := &web.SiteConfig{} - if existing != nil { - expanded = existing - } - - linuxSiteConfig := siteConfig[0] - - if servicePlan.Sku != nil && servicePlan.Sku.Name != nil { - if isFreeOrSharedServicePlan(*servicePlan.Sku.Name) { - if linuxSiteConfig.AlwaysOn { - return nil, fmt.Errorf("always_on cannot be set to true when using Free, F1, D1 Sku") - } - if expanded.AlwaysOn != nil && *expanded.AlwaysOn { - return nil, fmt.Errorf("always_on feature has to be turned off before switching to a free/shared Sku") - } - } - } - expanded.AlwaysOn = utils.Bool(linuxSiteConfig.AlwaysOn) - - 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") { - if len(linuxSiteConfig.ApplicationStack) == 1 { - linuxAppStack := linuxSiteConfig.ApplicationStack[0] - if linuxAppStack.NetFrameworkVersion != "" { - expanded.LinuxFxVersion = utils.String(fmt.Sprintf("DOTNETCORE|%s", linuxAppStack.NetFrameworkVersion)) - } - - if linuxAppStack.PhpVersion != "" { - expanded.LinuxFxVersion = utils.String(fmt.Sprintf("PHP|%s", linuxAppStack.PhpVersion)) - } - - if linuxAppStack.NodeVersion != "" { - expanded.LinuxFxVersion = utils.String(fmt.Sprintf("NODE|%s", linuxAppStack.NodeVersion)) - } - - if linuxAppStack.RubyVersion != "" { - expanded.LinuxFxVersion = utils.String(fmt.Sprintf("RUBY|%s", linuxAppStack.RubyVersion)) - } - - if linuxAppStack.PythonVersion != "" { - expanded.LinuxFxVersion = utils.String(fmt.Sprintf("PYTHON|%s", linuxAppStack.PythonVersion)) - } - - if linuxAppStack.JavaServer != "" { - // (@jackofallops) - Java has some special cases for Java SE when using specific versions of the runtime, resulting in this string - // being formatted in the form: `JAVA|u242` instead of the standard pattern of `JAVA|u242-java8` for example. This applies to jre8 and java11. - if linuxAppStack.JavaServer == "JAVA" && linuxAppStack.JavaServerVersion == "" { - expanded.LinuxFxVersion = utils.String(fmt.Sprintf("%s|%s", linuxAppStack.JavaServer, linuxAppStack.JavaVersion)) - } else { - expanded.LinuxFxVersion = utils.String(fmt.Sprintf("%s|%s-%s", linuxAppStack.JavaServer, linuxAppStack.JavaServerVersion, linuxAppStack.JavaVersion)) - } - } - - if linuxAppStack.DockerImage != "" { - expanded.LinuxFxVersion = utils.String(fmt.Sprintf("DOCKER|%s:%s", linuxAppStack.DockerImage, linuxAppStack.DockerImageTag)) - } - } else { - expanded.LinuxFxVersion = utils.String("") - } - } - - expanded.AcrUseManagedIdentityCreds = utils.Bool(linuxSiteConfig.UseManagedIdentityACR) - - 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 - } - - 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 - } - - 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 - } - - expanded.LocalMySQLEnabled = utils.Bool(linuxSiteConfig.LocalMysql) - - 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) - } - - expanded.RemoteDebuggingEnabled = utils.Bool(linuxSiteConfig.RemoteDebugging) - - if metadata.ResourceData.HasChange("site_config.0.remote_debugging_version") { - expanded.RemoteDebuggingVersion = utils.String(linuxSiteConfig.RemoteDebuggingVersion) - } - - expanded.Use32BitWorkerProcess = utils.Bool(linuxSiteConfig.Use32BitWorker) - - expanded.WebSocketsEnabled = utils.Bool(linuxSiteConfig.WebSockets) - - if metadata.ResourceData.HasChange("site_config.0.ftps_state") { - expanded.FtpsState = web.FtpsState(linuxSiteConfig.FtpsState) - } - - if metadata.ResourceData.HasChange("site_config.0.health_check_path") { - expanded.HealthCheckPath = utils.String(linuxSiteConfig.HealthCheckPath) - } - - if metadata.ResourceData.HasChange("site_config.0.worker_count") { - expanded.NumberOfWorkers = utils.Int32(int32(linuxSiteConfig.NumberOfWorkers)) - } - - if metadata.ResourceData.HasChange("site_config.0.minimum_tls_version") { - expanded.MinTLSVersion = web.SupportedTLSVersions(linuxSiteConfig.MinTlsVersion) - } - - 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) - if cors == nil { - cors = &web.CorsSettings{ - AllowedOrigins: &[]string{}, - } - } - expanded.Cors = cors - } - - expanded.AutoHealEnabled = utils.Bool(linuxSiteConfig.AutoHeal) - - if metadata.ResourceData.HasChange("site_config.0.auto_heal_setting") { - expanded.AutoHealRules = expandAutoHealSettingsLinux(linuxSiteConfig.AutoHealSettings) - } - - if metadata.ResourceData.HasChange("site_config.0.vnet_route_all_enabled") { - expanded.VnetRouteAllEnabled = utils.Bool(linuxSiteConfig.VnetRouteAllEnabled) - } - - return expanded, nil -} - -func ExpandLogsConfig(config []LogsConfig) *web.SiteLogsConfig { - result := &web.SiteLogsConfig{} - if len(config) == 0 { - return result - } - - result.SiteLogsConfigProperties = &web.SiteLogsConfigProperties{} - - logsConfig := config[0] - - if len(logsConfig.ApplicationLogs) == 1 { - appLogs := logsConfig.ApplicationLogs[0] - result.SiteLogsConfigProperties.ApplicationLogs = &web.ApplicationLogsConfig{ - FileSystem: &web.FileSystemApplicationLogsConfig{ - Level: web.LogLevel(appLogs.FileSystemLevel), - }, - } - if len(appLogs.AzureBlobStorage) == 1 { - appLogsBlobs := appLogs.AzureBlobStorage[0] - result.SiteLogsConfigProperties.ApplicationLogs.AzureBlobStorage = &web.AzureBlobStorageApplicationLogsConfig{ - Level: web.LogLevel(appLogsBlobs.Level), - SasURL: utils.String(appLogsBlobs.SasUrl), - RetentionInDays: utils.Int32(int32(appLogsBlobs.RetentionInDays)), - } - } - } - - if len(logsConfig.HttpLogs) == 1 { - httpLogs := logsConfig.HttpLogs[0] - result.HTTPLogs = &web.HTTPLogsConfig{} - - if len(httpLogs.FileSystems) == 1 { - httpLogFileSystem := httpLogs.FileSystems[0] - result.HTTPLogs.FileSystem = &web.FileSystemHTTPLogsConfig{ - Enabled: utils.Bool(true), - RetentionInMb: utils.Int32(int32(httpLogFileSystem.RetentionMB)), - RetentionInDays: utils.Int32(int32(httpLogFileSystem.RetentionDays)), - } - } - - if len(httpLogs.AzureBlobStorage) == 1 { - httpLogsBlobStorage := httpLogs.AzureBlobStorage[0] - result.HTTPLogs.AzureBlobStorage = &web.AzureBlobStorageHTTPLogsConfig{ - Enabled: utils.Bool(httpLogsBlobStorage.SasUrl != ""), - SasURL: utils.String(httpLogsBlobStorage.SasUrl), - RetentionInDays: utils.Int32(int32(httpLogsBlobStorage.RetentionInDays)), - } - } - } - - result.DetailedErrorMessages = &web.EnabledConfig{ - Enabled: utils.Bool(logsConfig.DetailedErrorMessages), - } - - result.FailedRequestsTracing = &web.EnabledConfig{ - Enabled: utils.Bool(logsConfig.FailedRequestTracing), - } - - return result -} - -func ExpandBackupConfig(backupConfigs []Backup) *web.BackupRequest { - result := &web.BackupRequest{} - if len(backupConfigs) == 0 { - return result - } - - backupConfig := backupConfigs[0] - backupSchedule := backupConfig.Schedule[0] - result.BackupRequestProperties = &web.BackupRequestProperties{ - Enabled: utils.Bool(backupConfig.Enabled), - BackupName: utils.String(backupConfig.Name), - StorageAccountURL: utils.String(backupConfig.StorageAccountUrl), - BackupSchedule: &web.BackupSchedule{ - FrequencyInterval: utils.Int32(int32(backupSchedule.FrequencyInterval)), - FrequencyUnit: web.FrequencyUnit(backupSchedule.FrequencyUnit), - KeepAtLeastOneBackup: utils.Bool(backupSchedule.KeepAtLeastOneBackup), - RetentionPeriodInDays: utils.Int32(int32(backupSchedule.RetentionPeriodDays)), - }, - } - - if backupSchedule.StartTime != "" { - dateTimeToStart, _ := time.Parse(time.RFC3339, backupSchedule.StartTime) - result.BackupRequestProperties.BackupSchedule.StartTime = &date.Time{Time: dateTimeToStart} - } - - return result -} - -func ExpandStorageConfig(storageConfigs []StorageAccount) *web.AzureStoragePropertyDictionaryResource { - storageAccounts := make(map[string]*web.AzureStorageInfoValue) - result := &web.AzureStoragePropertyDictionaryResource{} - if len(storageConfigs) == 0 { - result.Properties = storageAccounts - return result - } - - for _, v := range storageConfigs { - storageAccounts[v.Name] = &web.AzureStorageInfoValue{ - Type: web.AzureStorageType(v.Type), - AccountName: utils.String(v.AccountName), - ShareName: utils.String(v.ShareName), - AccessKey: utils.String(v.AccessKey), - MountPath: utils.String(v.MountPath), - } - } - - result.Properties = storageAccounts - - return result -} - -func ExpandConnectionStrings(connectionStringsConfig []ConnectionString) *web.ConnectionStringDictionary { - result := &web.ConnectionStringDictionary{} - if len(connectionStringsConfig) == 0 { - return result - } - - connectionStrings := make(map[string]*web.ConnStringValueTypePair) - for _, v := range connectionStringsConfig { - connectionStrings[v.Name] = &web.ConnStringValueTypePair{ - Value: utils.String(v.Value), - Type: web.ConnectionStringType(v.Type), - } - } - result.Properties = connectionStrings - - return result -} - -func expandVirtualApplications(virtualApplicationConfig []VirtualApplication) *[]web.VirtualApplication { - if len(virtualApplicationConfig) == 0 { - return nil - } - - result := make([]web.VirtualApplication, 0) - - for _, v := range virtualApplicationConfig { - virtualApp := web.VirtualApplication{ - VirtualPath: utils.String(v.VirtualPath), - PhysicalPath: utils.String(v.PhysicalPath), - PreloadEnabled: utils.Bool(v.Preload), - } - if len(v.VirtualDirectories) > 0 { - virtualDirs := make([]web.VirtualDirectory, 0) - for _, d := range v.VirtualDirectories { - virtualDirs = append(virtualDirs, web.VirtualDirectory{ - VirtualPath: utils.String(d.VirtualPath), - PhysicalPath: utils.String(d.PhysicalPath), - }) - } - virtualApp.VirtualDirectories = &virtualDirs - } - - result = append(result, virtualApp) - } - return &result -} - -func expandVirtualApplicationsForUpdate(virtualApplicationConfig []VirtualApplication) *[]web.VirtualApplication { - if len(virtualApplicationConfig) == 0 { - // to remove this block from the config we need to give the service the original default back, sending an empty struct leaves the previous config in place - return &[]web.VirtualApplication{ - { - VirtualPath: utils.String("/"), - PhysicalPath: utils.String("site\\wwwroot"), - PreloadEnabled: utils.Bool(true), - }, - } - } - - result := make([]web.VirtualApplication, 0) - - for _, v := range virtualApplicationConfig { - virtualApp := web.VirtualApplication{ - VirtualPath: utils.String(v.VirtualPath), - PhysicalPath: utils.String(v.PhysicalPath), - PreloadEnabled: utils.Bool(v.Preload), - } - if len(v.VirtualDirectories) > 0 { - virtualDirs := make([]web.VirtualDirectory, 0) - for _, d := range v.VirtualDirectories { - virtualDirs = append(virtualDirs, web.VirtualDirectory{ - VirtualPath: utils.String(d.VirtualPath), - PhysicalPath: utils.String(d.PhysicalPath), - }) - } - virtualApp.VirtualDirectories = &virtualDirs - } - - result = append(result, virtualApp) - } - return &result -} - -func FlattenBackupConfig(backupRequest web.BackupRequest) []Backup { - if backupRequest.BackupRequestProperties == nil { - return nil - } - props := *backupRequest.BackupRequestProperties - backup := Backup{} - if props.BackupName != nil { - backup.Name = *props.BackupName - } - - if props.StorageAccountURL != nil { - backup.StorageAccountUrl = *props.StorageAccountURL - } - - if props.Enabled != nil { - backup.Enabled = *props.Enabled - } - - if schedule := props.BackupSchedule; schedule != nil { - backupSchedule := BackupSchedule{ - FrequencyUnit: string(schedule.FrequencyUnit), - } - if schedule.FrequencyInterval != nil { - backupSchedule.FrequencyInterval = int(*schedule.FrequencyInterval) - } - - if schedule.KeepAtLeastOneBackup != nil { - backupSchedule.KeepAtLeastOneBackup = *schedule.KeepAtLeastOneBackup - } - - if schedule.RetentionPeriodInDays != nil { - backupSchedule.RetentionPeriodDays = int(*schedule.RetentionPeriodInDays) - } - - if schedule.StartTime != nil && !schedule.StartTime.IsZero() { - backupSchedule.StartTime = schedule.StartTime.Format(time.RFC3339) - } - - if schedule.LastExecutionTime != nil && !schedule.LastExecutionTime.IsZero() { - backupSchedule.LastExecutionTime = schedule.LastExecutionTime.Format(time.RFC3339) - } - - backup.Schedule = []BackupSchedule{backupSchedule} - } - - return []Backup{backup} -} - -func FlattenLogsConfig(logsConfig web.SiteLogsConfig) []LogsConfig { - if logsConfig.SiteLogsConfigProperties == nil { - return nil - } - props := *logsConfig.SiteLogsConfigProperties - if onlyDefaultLoggingConfig(props) { - return nil - } - - logs := LogsConfig{} - - if props.ApplicationLogs != nil { - appLogs := *props.ApplicationLogs - applicationLog := ApplicationLog{} - - if appLogs.FileSystem != nil && appLogs.FileSystem.Level != web.LogLevelOff { - applicationLog.FileSystemLevel = string(appLogs.FileSystem.Level) - if appLogs.AzureBlobStorage != nil && appLogs.AzureBlobStorage.Level != web.LogLevelOff { - blobStorage := AzureBlobStorage{ - Level: string(appLogs.AzureBlobStorage.Level), - } - - blobStorage.SasUrl = utils.NormalizeNilableString(appLogs.AzureBlobStorage.SasURL) - - blobStorage.RetentionInDays = int(utils.NormaliseNilableInt32(appLogs.AzureBlobStorage.RetentionInDays)) - - applicationLog.AzureBlobStorage = []AzureBlobStorage{blobStorage} - } - logs.ApplicationLogs = []ApplicationLog{applicationLog} - } - } - - if props.HTTPLogs != nil { - httpLogs := *props.HTTPLogs - httpLog := HttpLog{} - - if httpLogs.FileSystem != nil && (httpLogs.FileSystem.Enabled != nil && *httpLogs.FileSystem.Enabled) { - fileSystem := LogsFileSystem{} - if httpLogs.FileSystem.RetentionInMb != nil { - fileSystem.RetentionMB = int(*httpLogs.FileSystem.RetentionInMb) - } - - if httpLogs.FileSystem.RetentionInDays != nil { - fileSystem.RetentionDays = int(*httpLogs.FileSystem.RetentionInDays) - } - - httpLog.FileSystems = []LogsFileSystem{fileSystem} - } - - if httpLogs.AzureBlobStorage != nil && (httpLogs.AzureBlobStorage.Enabled != nil && *httpLogs.AzureBlobStorage.Enabled) { - blobStorage := AzureBlobStorageHttp{} - if httpLogs.AzureBlobStorage.SasURL != nil { - blobStorage.SasUrl = *httpLogs.AzureBlobStorage.SasURL - } - - if httpLogs.AzureBlobStorage.RetentionInDays != nil { - blobStorage.RetentionInDays = int(*httpLogs.AzureBlobStorage.RetentionInDays) - } - - if blobStorage.RetentionInDays != 0 && blobStorage.SasUrl != "" { - httpLog.AzureBlobStorage = []AzureBlobStorageHttp{blobStorage} - } - } - - if httpLog.FileSystems != nil || httpLog.AzureBlobStorage != nil { - logs.HttpLogs = []HttpLog{httpLog} - } - } - - // logs.DetailedErrorMessages = false - if props.DetailedErrorMessages != nil && props.DetailedErrorMessages.Enabled != nil { - logs.DetailedErrorMessages = *props.DetailedErrorMessages.Enabled - } - - // logs.FailedRequestTracing = false - if props.FailedRequestsTracing != nil && props.FailedRequestsTracing.Enabled != nil { - logs.FailedRequestTracing = *props.FailedRequestsTracing.Enabled - } - - return []LogsConfig{logs} -} - -func onlyDefaultLoggingConfig(props web.SiteLogsConfigProperties) bool { - if props.ApplicationLogs == nil || props.HTTPLogs == nil || props.FailedRequestsTracing == nil || props.DetailedErrorMessages == nil { - return false - } - if props.ApplicationLogs.FileSystem != nil && props.ApplicationLogs.FileSystem.Level != web.LogLevelOff { - return false - } - if props.ApplicationLogs.AzureBlobStorage != nil && props.ApplicationLogs.AzureBlobStorage.Level != web.LogLevelOff { - return false - } - if props.HTTPLogs.FileSystem != nil && props.HTTPLogs.FileSystem.Enabled != nil && (*props.HTTPLogs.FileSystem.Enabled) { - return false - } - if props.HTTPLogs.AzureBlobStorage != nil && props.HTTPLogs.AzureBlobStorage.Enabled != nil && (*props.HTTPLogs.AzureBlobStorage.Enabled) { - return false - } - if props.FailedRequestsTracing.Enabled == nil || *props.FailedRequestsTracing.Enabled { - return false - } - if props.DetailedErrorMessages.Enabled == nil || *props.DetailedErrorMessages.Enabled { - return false - } - return true -} - -func FlattenSiteConfigWindows(appSiteConfig *web.SiteConfig, currentStack string, healthCheckCount *int) []SiteConfigWindows { - if appSiteConfig == nil { - return nil - } - - siteConfig := SiteConfigWindows{ - AlwaysOn: utils.NormaliseNilableBool(appSiteConfig.AlwaysOn), - AppCommandLine: utils.NormalizeNilableString(appSiteConfig.AppCommandLine), - AutoHeal: utils.NormaliseNilableBool(appSiteConfig.AutoHealEnabled), - AutoHealSettings: flattenAutoHealSettingsWindows(appSiteConfig.AutoHealRules), - ContainerRegistryUserMSI: utils.NormalizeNilableString(appSiteConfig.AcrUserManagedIdentityID), - DetailedErrorLogging: utils.NormaliseNilableBool(appSiteConfig.DetailedErrorLoggingEnabled), - FtpsState: string(appSiteConfig.FtpsState), - HealthCheckPath: utils.NormalizeNilableString(appSiteConfig.HealthCheckPath), - HealthCheckEvictionTime: utils.NormaliseNilableInt(healthCheckCount), - Http2Enabled: utils.NormaliseNilableBool(appSiteConfig.HTTP20Enabled), - IpRestriction: FlattenIpRestrictions(appSiteConfig.IPSecurityRestrictions), - LoadBalancing: string(appSiteConfig.LoadBalancing), - LocalMysql: utils.NormaliseNilableBool(appSiteConfig.LocalMySQLEnabled), - ManagedPipelineMode: string(appSiteConfig.ManagedPipelineMode), - MinTlsVersion: string(appSiteConfig.MinTLSVersion), - WorkerCount: int(utils.NormaliseNilableInt32(appSiteConfig.NumberOfWorkers)), - RemoteDebugging: utils.NormaliseNilableBool(appSiteConfig.RemoteDebuggingEnabled), - RemoteDebuggingVersion: strings.ToUpper(utils.NormalizeNilableString(appSiteConfig.RemoteDebuggingVersion)), - ScmIpRestriction: FlattenIpRestrictions(appSiteConfig.ScmIPSecurityRestrictions), - ScmMinTlsVersion: string(appSiteConfig.ScmMinTLSVersion), - ScmType: string(appSiteConfig.ScmType), - ScmUseMainIpRestriction: utils.NormaliseNilableBool(appSiteConfig.ScmIPSecurityRestrictionsUseMain), - Use32BitWorker: utils.NormaliseNilableBool(appSiteConfig.Use32BitWorkerProcess), - 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 { - siteConfig.ApiManagementConfigId = *appSiteConfig.APIManagementConfig.ID - } - - if appSiteConfig.APIDefinition != nil && appSiteConfig.APIDefinition.URL != nil { - siteConfig.ApiDefinition = *appSiteConfig.APIDefinition.URL - } - - if appSiteConfig.DefaultDocuments != nil { - siteConfig.DefaultDocuments = *appSiteConfig.DefaultDocuments - } - - if appSiteConfig.NumberOfWorkers != nil { - siteConfig.WorkerCount = int(*appSiteConfig.NumberOfWorkers) - } - - var winAppStack ApplicationStackWindows - winAppStack.NetFrameworkVersion = utils.NormalizeNilableString(appSiteConfig.NetFrameworkVersion) - winAppStack.PhpVersion = utils.NormalizeNilableString(appSiteConfig.PhpVersion) - winAppStack.NodeVersion = utils.NormalizeNilableString(appSiteConfig.NodeVersion) - winAppStack.PythonVersion = utils.NormalizeNilableString(appSiteConfig.PythonVersion) - winAppStack.JavaVersion = utils.NormalizeNilableString(appSiteConfig.JavaVersion) - winAppStack.JavaContainer = utils.NormalizeNilableString(appSiteConfig.JavaContainer) - winAppStack.JavaContainerVersion = utils.NormalizeNilableString(appSiteConfig.JavaContainerVersion) - - siteConfig.WindowsFxVersion = utils.NormalizeNilableString(appSiteConfig.WindowsFxVersion) - if siteConfig.WindowsFxVersion != "" { - // Decode the string to docker values - parts := strings.Split(strings.TrimPrefix(siteConfig.WindowsFxVersion, "DOCKER|"), ":") - if len(parts) == 2 { - winAppStack.DockerContainerTag = parts[1] - path := strings.Split(parts[0], "/") - if len(path) > 1 { - winAppStack.DockerContainerRegistry = path[0] - winAppStack.DockerContainerName = strings.TrimPrefix(parts[0], fmt.Sprintf("%s/", path[0])) - } else { - winAppStack.DockerContainerName = path[0] - } - } - } - winAppStack.CurrentStack = currentStack - - siteConfig.ApplicationStack = []ApplicationStackWindows{winAppStack} - - if appSiteConfig.Cors != nil { - cors := CorsSetting{} - corsSettings := appSiteConfig.Cors - if corsSettings.SupportCredentials != nil { - cors.SupportCredentials = *corsSettings.SupportCredentials - } - - if corsSettings.AllowedOrigins != nil && len(*corsSettings.AllowedOrigins) != 0 { - cors.AllowedOrigins = *corsSettings.AllowedOrigins - } - siteConfig.Cors = []CorsSetting{cors} - } - - return []SiteConfigWindows{siteConfig} -} - -func FlattenSiteConfigLinux(appSiteConfig *web.SiteConfig, healthCheckCount *int) []SiteConfigLinux { - if appSiteConfig == nil { - return nil - } - - siteConfig := SiteConfigLinux{ - AlwaysOn: utils.NormaliseNilableBool(appSiteConfig.AlwaysOn), - AppCommandLine: utils.NormalizeNilableString(appSiteConfig.AppCommandLine), - AutoHeal: utils.NormaliseNilableBool(appSiteConfig.AutoHealEnabled), - AutoHealSettings: flattenAutoHealSettingsLinux(appSiteConfig.AutoHealRules), - ContainerRegistryMSI: utils.NormalizeNilableString(appSiteConfig.AcrUserManagedIdentityID), - DetailedErrorLogging: utils.NormaliseNilableBool(appSiteConfig.DetailedErrorLoggingEnabled), - Http2Enabled: utils.NormaliseNilableBool(appSiteConfig.HTTP20Enabled), - IpRestriction: FlattenIpRestrictions(appSiteConfig.IPSecurityRestrictions), - ManagedPipelineMode: string(appSiteConfig.ManagedPipelineMode), - ScmType: string(appSiteConfig.ScmType), - FtpsState: string(appSiteConfig.FtpsState), - HealthCheckPath: utils.NormalizeNilableString(appSiteConfig.HealthCheckPath), - HealthCheckEvictionTime: utils.NormaliseNilableInt(healthCheckCount), - LoadBalancing: string(appSiteConfig.LoadBalancing), - LocalMysql: utils.NormaliseNilableBool(appSiteConfig.LocalMySQLEnabled), - MinTlsVersion: string(appSiteConfig.MinTLSVersion), - NumberOfWorkers: int(utils.NormaliseNilableInt32(appSiteConfig.NumberOfWorkers)), - RemoteDebugging: utils.NormaliseNilableBool(appSiteConfig.RemoteDebuggingEnabled), - RemoteDebuggingVersion: strings.ToUpper(utils.NormalizeNilableString(appSiteConfig.RemoteDebuggingVersion)), - ScmIpRestriction: FlattenIpRestrictions(appSiteConfig.ScmIPSecurityRestrictions), - ScmMinTlsVersion: string(appSiteConfig.ScmMinTLSVersion), - ScmUseMainIpRestriction: utils.NormaliseNilableBool(appSiteConfig.ScmIPSecurityRestrictionsUseMain), - Use32BitWorker: utils.NormaliseNilableBool(appSiteConfig.Use32BitWorkerProcess), - UseManagedIdentityACR: utils.NormaliseNilableBool(appSiteConfig.AcrUseManagedIdentityCreds), - WebSockets: utils.NormaliseNilableBool(appSiteConfig.WebSocketsEnabled), - VnetRouteAllEnabled: utils.NormaliseNilableBool(appSiteConfig.VnetRouteAllEnabled), - } - - if appSiteConfig.APIManagementConfig != nil && appSiteConfig.APIManagementConfig.ID != nil { - siteConfig.ApiManagementConfigId = *appSiteConfig.APIManagementConfig.ID - } - - if appSiteConfig.APIDefinition != nil && appSiteConfig.APIDefinition.URL != nil { - siteConfig.ApiDefinition = *appSiteConfig.APIDefinition.URL - } - - if appSiteConfig.DefaultDocuments != nil { - siteConfig.DefaultDocuments = *appSiteConfig.DefaultDocuments - } - - if appSiteConfig.LinuxFxVersion != nil { - var linuxAppStack ApplicationStackLinux - siteConfig.LinuxFxVersion = *appSiteConfig.LinuxFxVersion - // Decode the string to docker values - linuxAppStack = decodeApplicationStackLinux(siteConfig.LinuxFxVersion) - siteConfig.ApplicationStack = []ApplicationStackLinux{linuxAppStack} - } - - if appSiteConfig.Cors != nil { - corsSettings := appSiteConfig.Cors - cors := CorsSetting{} - if corsSettings.SupportCredentials != nil { - cors.SupportCredentials = *corsSettings.SupportCredentials - } - - if corsSettings.AllowedOrigins != nil && len(*corsSettings.AllowedOrigins) != 0 { - cors.AllowedOrigins = *corsSettings.AllowedOrigins - } - siteConfig.Cors = []CorsSetting{cors} - } - - return []SiteConfigLinux{siteConfig} -} - -func FlattenStorageAccounts(appStorageAccounts web.AzureStoragePropertyDictionaryResource) []StorageAccount { - if len(appStorageAccounts.Properties) == 0 { - return nil - } - var storageAccounts []StorageAccount - for k, v := range appStorageAccounts.Properties { - storageAccount := StorageAccount{ - Name: k, - Type: string(v.Type), - } - if v.AccountName != nil { - storageAccount.AccountName = *v.AccountName - } - - if v.ShareName != nil { - storageAccount.ShareName = *v.ShareName - } - - if v.AccessKey != nil { - storageAccount.AccessKey = *v.AccessKey - } - - if v.MountPath != nil { - storageAccount.MountPath = *v.MountPath - } - - storageAccounts = append(storageAccounts, storageAccount) - } - - return storageAccounts -} - -func FlattenConnectionStrings(appConnectionStrings web.ConnectionStringDictionary) []ConnectionString { - if len(appConnectionStrings.Properties) == 0 { - return nil - } - var connectionStrings []ConnectionString - for k, v := range appConnectionStrings.Properties { - connectionString := ConnectionString{ - Name: k, - Type: string(v.Type), - } - if v.Value != nil { - connectionString.Value = *v.Value - } - connectionStrings = append(connectionStrings, connectionString) - } - - return connectionStrings -} - -func ExpandAppSettingsForUpdate(settings map[string]string) *web.StringDictionary { - appSettings := make(map[string]*string) - for k, v := range settings { - appSettings[k] = utils.String(v) - } - - return &web.StringDictionary{ - Properties: appSettings, - } -} - -func ExpandAppSettingsForCreate(settings map[string]string) *[]web.NameValuePair { - if len(settings) > 0 { - result := make([]web.NameValuePair, 0) - for k, v := range settings { - result = append(result, web.NameValuePair{ - Name: utils.String(k), - Value: utils.String(v), - }) - } - return &result - } - return nil -} - -func FlattenAppSettings(input web.StringDictionary) (map[string]string, *int) { - maxPingFailures := "WEBSITE_HEALTHCHECK_MAXPINGFAILURE" - unmanagedSettings := []string{ - "DIAGNOSTICS_AZUREBLOBCONTAINERSASURL", - "DIAGNOSTICS_AZUREBLOBRETENTIONINDAYS", - "WEBSITE_HTTPLOGGING_CONTAINER_URL", - "WEBSITE_HTTPLOGGING_RETENTION_DAYS", - "WEBSITE_VNET_ROUTE_ALL", - "spring.datasource.password", - "spring.datasource.url", - "spring.datasource.username", - maxPingFailures, - } - - var healthCheckCount *int - appSettings := FlattenWebStringDictionary(input) - if v, ok := appSettings[maxPingFailures]; ok { - h, _ := strconv.Atoi(v) - healthCheckCount = &h - } - - // Remove the settings the service adds for legacy reasons. - for _, v := range unmanagedSettings { //nolint:typecheck - delete(appSettings, v) - } - - return appSettings, healthCheckCount -} - -func flattenVirtualApplications(appVirtualApplications *[]web.VirtualApplication) []VirtualApplication { - if appVirtualApplications == nil || onlyDefaultVirtualApplication(*appVirtualApplications) { - return nil - } - - var virtualApplications []VirtualApplication - for _, v := range *appVirtualApplications { - virtualApp := VirtualApplication{ - VirtualPath: utils.NormalizeNilableString(v.VirtualPath), - PhysicalPath: utils.NormalizeNilableString(v.PhysicalPath), - } - if preload := v.PreloadEnabled; preload != nil { - virtualApp.Preload = *preload - } - if v.VirtualDirectories != nil && len(*v.VirtualDirectories) > 0 { - virtualDirs := make([]VirtualDirectory, 0) - for _, d := range *v.VirtualDirectories { - virtualDir := VirtualDirectory{ - VirtualPath: utils.NormalizeNilableString(d.VirtualPath), - PhysicalPath: utils.NormalizeNilableString(d.PhysicalPath), - } - virtualDirs = append(virtualDirs, virtualDir) - } - virtualApp.VirtualDirectories = virtualDirs - } - virtualApplications = append(virtualApplications, virtualApp) - } - - return virtualApplications -} - -func onlyDefaultVirtualApplication(input []web.VirtualApplication) bool { - if len(input) > 1 { - return false - } - app := input[0] - if app.VirtualPath == nil || app.PhysicalPath == nil { - return false - } - if *app.VirtualPath == "/" && *app.PhysicalPath == "site\\wwwroot" && *app.PreloadEnabled && app.VirtualDirectories == nil { - return true - } - return false -} - -func expandAutoHealSettingsWindows(autoHealSettings []AutoHealSettingWindows) *web.AutoHealRules { - if len(autoHealSettings) == 0 { - return &web.AutoHealRules{} - } - - result := &web.AutoHealRules{ - Triggers: &web.AutoHealTriggers{}, - Actions: &web.AutoHealActions{}, - } - - autoHeal := autoHealSettings[0] - - triggers := autoHeal.Triggers[0] - if len(triggers.Requests) == 1 { - result.Triggers.Requests = &web.RequestsBasedTrigger{ - Count: utils.Int32(int32(triggers.Requests[0].Count)), - TimeInterval: utils.String(triggers.Requests[0].Interval), - } - } - - if len(triggers.SlowRequests) == 1 { - result.Triggers.SlowRequests = &web.SlowRequestsBasedTrigger{ - TimeTaken: utils.String(triggers.SlowRequests[0].TimeTaken), - TimeInterval: utils.String(triggers.SlowRequests[0].Interval), - Count: utils.Int32(int32(triggers.SlowRequests[0].Count)), - } - if triggers.SlowRequests[0].Path != "" { - result.Triggers.SlowRequests.Path = utils.String(triggers.SlowRequests[0].Path) - } - } - - if triggers.PrivateMemoryKB != 0 { - result.Triggers.PrivateBytesInKB = utils.Int32(int32(triggers.PrivateMemoryKB)) - } - - if len(triggers.StatusCodes) > 0 { - statusCodeTriggers := make([]web.StatusCodesBasedTrigger, 0) - statusCodeRangeTriggers := make([]web.StatusCodesRangeBasedTrigger, 0) - for _, s := range triggers.StatusCodes { - statusCodeTrigger := web.StatusCodesBasedTrigger{} - statusCodeRangeTrigger := web.StatusCodesRangeBasedTrigger{} - parts := strings.Split(s.StatusCodeRange, "-") - if len(parts) == 2 { - statusCodeRangeTrigger.StatusCodes = utils.String(s.StatusCodeRange) - statusCodeRangeTrigger.Count = utils.Int32(int32(s.Count)) - statusCodeRangeTrigger.TimeInterval = utils.String(s.Interval) - if s.Path != "" { - statusCodeRangeTrigger.Path = utils.String(s.Path) - } - statusCodeRangeTriggers = append(statusCodeRangeTriggers, statusCodeRangeTrigger) - } else { - statusCode, err := strconv.Atoi(s.StatusCodeRange) - if err == nil { - statusCodeTrigger.Status = utils.Int32(int32(statusCode)) - } - statusCodeTrigger.Count = utils.Int32(int32(s.Count)) - statusCodeTrigger.TimeInterval = utils.String(s.Interval) - if s.Path != "" { - statusCodeTrigger.Path = utils.String(s.Path) - } - statusCodeTriggers = append(statusCodeTriggers, statusCodeTrigger) - } - } - result.Triggers.StatusCodes = &statusCodeTriggers - result.Triggers.StatusCodesRange = &statusCodeRangeTriggers - } - - action := autoHeal.Actions[0] - result.Actions.ActionType = web.AutoHealActionType(action.ActionType) - result.Actions.MinProcessExecutionTime = utils.String(action.MinimumProcessTime) - if len(action.CustomAction) != 0 { - customAction := action.CustomAction[0] - result.Actions.CustomAction = &web.AutoHealCustomAction{ - Exe: utils.String(customAction.Executable), - Parameters: utils.String(customAction.Parameters), - } - } - - return result -} - -func flattenAutoHealSettingsWindows(autoHealRules *web.AutoHealRules) []AutoHealSettingWindows { - if autoHealRules == nil { - return nil - } - - result := AutoHealSettingWindows{} - // Triggers - if autoHealRules.Triggers != nil { - resultTrigger := AutoHealTriggerWindows{} - triggers := *autoHealRules.Triggers - if triggers.Requests != nil { - count := 0 - if triggers.Requests.Count != nil { - count = int(*triggers.Requests.Count) - } - resultTrigger.Requests = []AutoHealRequestTrigger{{ - Count: count, - Interval: utils.NormalizeNilableString(triggers.Requests.TimeInterval), - }} - } - - if privateBytes := triggers.PrivateBytesInKB; privateBytes != nil && *privateBytes != 0 { - resultTrigger.PrivateMemoryKB = int(*triggers.PrivateBytesInKB) - } - - statusCodeTriggers := make([]AutoHealStatusCodeTrigger, 0) - if triggers.StatusCodes != nil { - for _, s := range *triggers.StatusCodes { - t := AutoHealStatusCodeTrigger{ - Interval: utils.NormalizeNilableString(s.TimeInterval), - Path: utils.NormalizeNilableString(s.Path), - } - - if s.Status != nil { - t.StatusCodeRange = strconv.Itoa(int(*s.Status)) - } - - if s.Count != nil { - t.Count = int(*s.Count) - } - - if s.SubStatus != nil { - t.SubStatus = int(*s.SubStatus) - } - statusCodeTriggers = append(statusCodeTriggers, t) - } - } - if triggers.StatusCodesRange != nil { - for _, s := range *triggers.StatusCodesRange { - t := AutoHealStatusCodeTrigger{ - Interval: utils.NormalizeNilableString(s.TimeInterval), - Path: utils.NormalizeNilableString(s.Path), - } - if s.Count != nil { - t.Count = int(*s.Count) - } - - if s.StatusCodes != nil { - t.StatusCodeRange = *s.StatusCodes - } - statusCodeTriggers = append(statusCodeTriggers, t) - } - } - resultTrigger.StatusCodes = statusCodeTriggers - - slowRequestTriggers := make([]AutoHealSlowRequest, 0) - if triggers.SlowRequests != nil { - slowRequestTriggers = append(slowRequestTriggers, AutoHealSlowRequest{ - TimeTaken: utils.NormalizeNilableString(triggers.SlowRequests.TimeTaken), - Interval: utils.NormalizeNilableString(triggers.SlowRequests.TimeInterval), - Count: int(utils.NormaliseNilableInt32(triggers.SlowRequests.Count)), - Path: utils.NormalizeNilableString(triggers.SlowRequests.Path), - }) - } - resultTrigger.SlowRequests = slowRequestTriggers - result.Triggers = []AutoHealTriggerWindows{resultTrigger} - } - - // Actions - if autoHealRules.Actions != nil { - actions := *autoHealRules.Actions - customActions := make([]AutoHealCustomAction, 0) - if actions.CustomAction != nil { - customActions = append(customActions, AutoHealCustomAction{ - Executable: utils.NormalizeNilableString(actions.CustomAction.Exe), - Parameters: utils.NormalizeNilableString(actions.CustomAction.Parameters), - }) - } - - resultActions := AutoHealActionWindows{ - ActionType: string(actions.ActionType), - CustomAction: customActions, - MinimumProcessTime: utils.NormalizeNilableString(actions.MinProcessExecutionTime), - } - result.Actions = []AutoHealActionWindows{resultActions} - } - - if result.Actions != nil || result.Triggers != nil { - return []AutoHealSettingWindows{result} - } - - return nil -} - -func expandAutoHealSettingsLinux(autoHealSettings []AutoHealSettingLinux) *web.AutoHealRules { - if len(autoHealSettings) == 0 { - return nil - } - - result := &web.AutoHealRules{ - Triggers: &web.AutoHealTriggers{}, - Actions: &web.AutoHealActions{}, - } - - autoHeal := autoHealSettings[0] - - triggers := autoHeal.Triggers[0] - if len(triggers.Requests) == 1 { - result.Triggers.Requests = &web.RequestsBasedTrigger{ - Count: utils.Int32(int32(triggers.Requests[0].Count)), - TimeInterval: utils.String(triggers.Requests[0].Interval), - } - } - - if len(triggers.SlowRequests) == 1 { - result.Triggers.SlowRequests = &web.SlowRequestsBasedTrigger{ - TimeTaken: utils.String(triggers.SlowRequests[0].TimeTaken), - TimeInterval: utils.String(triggers.SlowRequests[0].Interval), - Count: utils.Int32(int32(triggers.SlowRequests[0].Count)), - } - if triggers.SlowRequests[0].Path != "" { - result.Triggers.SlowRequests.Path = utils.String(triggers.SlowRequests[0].Path) - } - } - - if len(triggers.StatusCodes) > 0 { - statusCodeTriggers := make([]web.StatusCodesBasedTrigger, 0) - statusCodeRangeTriggers := make([]web.StatusCodesRangeBasedTrigger, 0) - for _, s := range triggers.StatusCodes { - statusCodeTrigger := web.StatusCodesBasedTrigger{} - statusCodeRangeTrigger := web.StatusCodesRangeBasedTrigger{} - parts := strings.Split(s.StatusCodeRange, "-") - if len(parts) == 2 { - statusCodeRangeTrigger.StatusCodes = utils.String(s.StatusCodeRange) - statusCodeRangeTrigger.Count = utils.Int32(int32(s.Count)) - statusCodeRangeTrigger.TimeInterval = utils.String(s.Interval) - if s.Path != "" { - statusCodeRangeTrigger.Path = utils.String(s.Path) - } - statusCodeRangeTriggers = append(statusCodeRangeTriggers, statusCodeRangeTrigger) - } else { - statusCode, err := strconv.Atoi(s.StatusCodeRange) - if err == nil { - statusCodeTrigger.Status = utils.Int32(int32(statusCode)) - } - statusCodeTrigger.Count = utils.Int32(int32(s.Count)) - statusCodeTrigger.TimeInterval = utils.String(s.Interval) - if s.Path != "" { - statusCodeTrigger.Path = utils.String(s.Path) - } - statusCodeTriggers = append(statusCodeTriggers, statusCodeTrigger) - } - } - result.Triggers.StatusCodes = &statusCodeTriggers - result.Triggers.StatusCodesRange = &statusCodeRangeTriggers - } - - action := autoHeal.Actions[0] - result.Actions.ActionType = web.AutoHealActionType(action.ActionType) - result.Actions.MinProcessExecutionTime = utils.String(action.MinimumProcessTime) - - return result -} - -func flattenAutoHealSettingsLinux(autoHealRules *web.AutoHealRules) []AutoHealSettingLinux { - if autoHealRules == nil { - return nil - } - - result := AutoHealSettingLinux{} - - // Triggers - if autoHealRules.Triggers != nil { - resultTrigger := AutoHealTriggerLinux{} - triggers := *autoHealRules.Triggers - if triggers.Requests != nil { - count := 0 - if triggers.Requests.Count != nil { - count = int(*triggers.Requests.Count) - } - resultTrigger.Requests = []AutoHealRequestTrigger{{ - Count: count, - Interval: utils.NormalizeNilableString(triggers.Requests.TimeInterval), - }} - } - - statusCodeTriggers := make([]AutoHealStatusCodeTrigger, 0) - if triggers.StatusCodes != nil { - for _, s := range *triggers.StatusCodes { - t := AutoHealStatusCodeTrigger{ - Interval: utils.NormalizeNilableString(s.TimeInterval), - Path: utils.NormalizeNilableString(s.Path), - } - - if s.Status != nil { - t.StatusCodeRange = strconv.Itoa(int(*s.Status)) - } - - if s.Count != nil { - t.Count = int(*s.Count) - } - - if s.SubStatus != nil { - t.SubStatus = int(*s.SubStatus) - } - statusCodeTriggers = append(statusCodeTriggers, t) - } - } - if triggers.StatusCodesRange != nil { - for _, s := range *triggers.StatusCodesRange { - t := AutoHealStatusCodeTrigger{ - Interval: utils.NormalizeNilableString(s.TimeInterval), - Path: utils.NormalizeNilableString(s.Path), - } - if s.Count != nil { - t.Count = int(*s.Count) - } - - if s.StatusCodes != nil { - t.StatusCodeRange = *s.StatusCodes - } - statusCodeTriggers = append(statusCodeTriggers, t) - } - } - resultTrigger.StatusCodes = statusCodeTriggers - - slowRequestTriggers := make([]AutoHealSlowRequest, 0) - if triggers.SlowRequests != nil { - slowRequestTriggers = append(slowRequestTriggers, AutoHealSlowRequest{ - TimeTaken: utils.NormalizeNilableString(triggers.SlowRequests.TimeTaken), - Interval: utils.NormalizeNilableString(triggers.SlowRequests.TimeInterval), - Count: int(utils.NormaliseNilableInt32(triggers.SlowRequests.Count)), - Path: utils.NormalizeNilableString(triggers.SlowRequests.Path), - }) - } - resultTrigger.SlowRequests = slowRequestTriggers - result.Triggers = []AutoHealTriggerLinux{resultTrigger} - } - - // Actions - if autoHealRules.Actions != nil { - actions := *autoHealRules.Actions - - result.Actions = []AutoHealActionLinux{{ - ActionType: string(actions.ActionType), - MinimumProcessTime: utils.NormalizeNilableString(actions.MinProcessExecutionTime), - }} - } - - if result.Triggers != nil || result.Actions != nil { - return []AutoHealSettingLinux{result} - } - - return nil -} - -func DisabledLogsConfig() *web.SiteLogsConfig { - return &web.SiteLogsConfig{ - SiteLogsConfigProperties: &web.SiteLogsConfigProperties{ - DetailedErrorMessages: &web.EnabledConfig{ - Enabled: utils.Bool(false), - }, - FailedRequestsTracing: &web.EnabledConfig{ - Enabled: utils.Bool(false), - }, - ApplicationLogs: &web.ApplicationLogsConfig{ - FileSystem: &web.FileSystemApplicationLogsConfig{ - Level: web.LogLevelOff, - }, - AzureBlobStorage: &web.AzureBlobStorageApplicationLogsConfig{ - Level: web.LogLevelOff, - }, - }, - HTTPLogs: &web.HTTPLogsConfig{ - FileSystem: &web.FileSystemHTTPLogsConfig{ - Enabled: utils.Bool(false), - }, - AzureBlobStorage: &web.AzureBlobStorageHTTPLogsConfig{ - Enabled: utils.Bool(false), - }, - }, - }, - } -} - -func isFreeOrSharedServicePlan(inputSKU string) bool { - result := false - for _, sku := range freeSkus { - if inputSKU == sku { - result = true - } - } - for _, sku := range sharedSkus { - if inputSKU == sku { - result = true - } - } - return result -} diff --git a/internal/services/appservice/helpers/web_app_slot_schema.go b/internal/services/appservice/helpers/web_app_slot_schema.go index 10ef51144ff6..ea4b9c39df1a 100644 --- a/internal/services/appservice/helpers/web_app_slot_schema.go +++ b/internal/services/appservice/helpers/web_app_slot_schema.go @@ -5,11 +5,11 @@ import ( "strings" "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2021-02-01/web" // nolint: staticcheck + "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" apimValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/apimanagement/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" ) type SiteConfigLinuxWebAppSlot struct { @@ -550,70 +550,68 @@ func ExpandSiteConfigLinuxWebAppSlot(siteConfig []SiteConfigLinuxWebAppSlot, exi linuxSlotSiteConfig := siteConfig[0] if metadata.ResourceData.HasChange("site_config.0.always_on") { - expanded.AlwaysOn = utils.Bool(linuxSlotSiteConfig.AlwaysOn) + expanded.AlwaysOn = pointer.To(linuxSlotSiteConfig.AlwaysOn) } if metadata.ResourceData.HasChange("site_config.0.api_management_api_id") { expanded.APIManagementConfig = &web.APIManagementConfig{ - ID: utils.String(linuxSlotSiteConfig.ApiManagementConfigId), + ID: pointer.To(linuxSlotSiteConfig.ApiManagementConfigId), } } if metadata.ResourceData.HasChange("site_config.0.api_definition_url") { expanded.APIDefinition = &web.APIDefinitionInfo{ - URL: utils.String(linuxSlotSiteConfig.ApiDefinition), + URL: pointer.To(linuxSlotSiteConfig.ApiDefinition), } } if metadata.ResourceData.HasChange("site_config.0.app_command_line") { - expanded.AppCommandLine = utils.String(linuxSlotSiteConfig.AppCommandLine) + expanded.AppCommandLine = pointer.To(linuxSlotSiteConfig.AppCommandLine) } if metadata.ResourceData.HasChange("site_config.0.application_stack") { if len(linuxSlotSiteConfig.ApplicationStack) == 1 { linuxAppStack := linuxSlotSiteConfig.ApplicationStack[0] if linuxAppStack.NetFrameworkVersion != "" { - expanded.LinuxFxVersion = utils.String(fmt.Sprintf("DOTNETCORE|%s", linuxAppStack.NetFrameworkVersion)) + expanded.LinuxFxVersion = pointer.To(fmt.Sprintf("DOTNETCORE|%s", linuxAppStack.NetFrameworkVersion)) } if linuxAppStack.PhpVersion != "" { - expanded.LinuxFxVersion = utils.String(fmt.Sprintf("PHP|%s", linuxAppStack.PhpVersion)) + expanded.LinuxFxVersion = pointer.To(fmt.Sprintf("PHP|%s", linuxAppStack.PhpVersion)) } if linuxAppStack.NodeVersion != "" { - expanded.LinuxFxVersion = utils.String(fmt.Sprintf("NODE|%s", linuxAppStack.NodeVersion)) + expanded.LinuxFxVersion = pointer.To(fmt.Sprintf("NODE|%s", linuxAppStack.NodeVersion)) } if linuxAppStack.PythonVersion != "" { - expanded.LinuxFxVersion = utils.String(fmt.Sprintf("PYTHON|%s", linuxAppStack.PythonVersion)) + expanded.LinuxFxVersion = pointer.To(fmt.Sprintf("PYTHON|%s", linuxAppStack.PythonVersion)) } if linuxAppStack.RubyVersion != "" { - expanded.LinuxFxVersion = utils.String(fmt.Sprintf("RUBY|%s", linuxAppStack.RubyVersion)) + expanded.LinuxFxVersion = pointer.To(fmt.Sprintf("RUBY|%s", linuxAppStack.RubyVersion)) } if linuxAppStack.JavaServer != "" { - // (@jackofallops) - Java has some special cases for Java SE when using specific versions of the runtime, resulting in this string - // being formatted in the form: `JAVA|u242` instead of the standard pattern of `JAVA|u242-java8` for example. This applies to jre8 and java11. - if linuxAppStack.JavaServer == "JAVA" && linuxAppStack.JavaServerVersion == "" { - expanded.LinuxFxVersion = utils.String(fmt.Sprintf("%s|%s", linuxAppStack.JavaServer, linuxAppStack.JavaVersion)) - } else { - expanded.LinuxFxVersion = utils.String(fmt.Sprintf("%s|%s-%s", linuxAppStack.JavaServer, linuxAppStack.JavaServerVersion, linuxAppStack.JavaVersion)) + javaString, err := JavaLinuxFxStringBuilder(linuxAppStack.JavaVersion, linuxAppStack.JavaServer, linuxAppStack.JavaServerVersion) + if err != nil { + return nil, fmt.Errorf("could not build linuxFxVersion string: %+v", err) } + expanded.LinuxFxVersion = javaString } if linuxAppStack.DockerImage != "" { - expanded.LinuxFxVersion = utils.String(fmt.Sprintf("DOCKER|%s:%s", linuxAppStack.DockerImage, linuxAppStack.DockerImageTag)) + expanded.LinuxFxVersion = pointer.To(fmt.Sprintf("DOCKER|%s:%s", linuxAppStack.DockerImage, linuxAppStack.DockerImageTag)) } } else { - expanded.LinuxFxVersion = utils.String("") + expanded.LinuxFxVersion = pointer.To("") } } - expanded.AcrUseManagedIdentityCreds = utils.Bool(linuxSlotSiteConfig.UseManagedIdentityACR) + expanded.AcrUseManagedIdentityCreds = pointer.To(linuxSlotSiteConfig.UseManagedIdentityACR) if metadata.ResourceData.HasChange("site_config.0.container_registry_managed_identity_client_id") { - expanded.AcrUserManagedIdentityID = utils.String(linuxSlotSiteConfig.ContainerRegistryMSI) + expanded.AcrUserManagedIdentityID = pointer.To(linuxSlotSiteConfig.ContainerRegistryMSI) } if metadata.ResourceData.HasChange("site_config.0.default_documents") { @@ -621,7 +619,7 @@ func ExpandSiteConfigLinuxWebAppSlot(siteConfig []SiteConfigLinuxWebAppSlot, exi } if metadata.ResourceData.HasChange("site_config.0.http2_enabled") { - expanded.HTTP20Enabled = utils.Bool(linuxSlotSiteConfig.Http2Enabled) + expanded.HTTP20Enabled = pointer.To(linuxSlotSiteConfig.Http2Enabled) } if metadata.ResourceData.HasChange("site_config.0.ip_restriction") { @@ -632,7 +630,7 @@ func ExpandSiteConfigLinuxWebAppSlot(siteConfig []SiteConfigLinuxWebAppSlot, exi expanded.IPSecurityRestrictions = ipRestrictions } - expanded.ScmIPSecurityRestrictionsUseMain = utils.Bool(linuxSlotSiteConfig.ScmUseMainIpRestriction) + expanded.ScmIPSecurityRestrictionsUseMain = pointer.To(linuxSlotSiteConfig.ScmUseMainIpRestriction) if metadata.ResourceData.HasChange("site_config.0.scm_ip_restriction") { scmIpRestrictions, err := ExpandIpRestrictions(linuxSlotSiteConfig.ScmIpRestriction) @@ -643,7 +641,7 @@ func ExpandSiteConfigLinuxWebAppSlot(siteConfig []SiteConfigLinuxWebAppSlot, exi } if metadata.ResourceData.HasChange("site_config.0.local_mysql_enabled") { - expanded.LocalMySQLEnabled = utils.Bool(linuxSlotSiteConfig.LocalMysql) + expanded.LocalMySQLEnabled = pointer.To(linuxSlotSiteConfig.LocalMysql) } if metadata.ResourceData.HasChange("site_config.0.load_balancing_mode") { @@ -655,19 +653,19 @@ func ExpandSiteConfigLinuxWebAppSlot(siteConfig []SiteConfigLinuxWebAppSlot, exi } if metadata.ResourceData.HasChange("site_config.0.remote_debugging_enabled") { - expanded.RemoteDebuggingEnabled = utils.Bool(linuxSlotSiteConfig.RemoteDebugging) + expanded.RemoteDebuggingEnabled = pointer.To(linuxSlotSiteConfig.RemoteDebugging) } if metadata.ResourceData.HasChange("site_config.0.remote_debugging_version") { - expanded.RemoteDebuggingVersion = utils.String(linuxSlotSiteConfig.RemoteDebuggingVersion) + expanded.RemoteDebuggingVersion = pointer.To(linuxSlotSiteConfig.RemoteDebuggingVersion) } if metadata.ResourceData.HasChange("site_config.0.use_32_bit_worker") { - expanded.Use32BitWorkerProcess = utils.Bool(linuxSlotSiteConfig.Use32BitWorker) + expanded.Use32BitWorkerProcess = pointer.To(linuxSlotSiteConfig.Use32BitWorker) } if metadata.ResourceData.HasChange("site_config.0.websockets_enabled") { - expanded.WebSocketsEnabled = utils.Bool(linuxSlotSiteConfig.WebSockets) + expanded.WebSocketsEnabled = pointer.To(linuxSlotSiteConfig.WebSockets) } if metadata.ResourceData.HasChange("site_config.0.ftps_state") { @@ -675,11 +673,11 @@ func ExpandSiteConfigLinuxWebAppSlot(siteConfig []SiteConfigLinuxWebAppSlot, exi } if metadata.ResourceData.HasChange("site_config.0.health_check_path") { - expanded.HealthCheckPath = utils.String(linuxSlotSiteConfig.HealthCheckPath) + expanded.HealthCheckPath = pointer.To(linuxSlotSiteConfig.HealthCheckPath) } if metadata.ResourceData.HasChange("site_config.0.worker_count") { - expanded.NumberOfWorkers = utils.Int32(int32(linuxSlotSiteConfig.WorkerCount)) + expanded.NumberOfWorkers = pointer.To(int32(linuxSlotSiteConfig.WorkerCount)) } if metadata.ResourceData.HasChange("site_config.0.minimum_tls_version") { @@ -691,7 +689,7 @@ func ExpandSiteConfigLinuxWebAppSlot(siteConfig []SiteConfigLinuxWebAppSlot, exi } if metadata.ResourceData.HasChange("site_config.0.auto_swap_slot_name") { - expanded.AutoSwapSlotName = utils.String(linuxSlotSiteConfig.AutoSwapSlotName) + expanded.AutoSwapSlotName = pointer.To(linuxSlotSiteConfig.AutoSwapSlotName) } if metadata.ResourceData.HasChange("site_config.0.cors") { @@ -705,7 +703,7 @@ func ExpandSiteConfigLinuxWebAppSlot(siteConfig []SiteConfigLinuxWebAppSlot, exi } if metadata.ResourceData.HasChange("site_config.0.auto_heal_enabled") { - expanded.AutoHealEnabled = utils.Bool(linuxSlotSiteConfig.AutoHeal) + expanded.AutoHealEnabled = pointer.To(linuxSlotSiteConfig.AutoHeal) } if metadata.ResourceData.HasChange("site_config.0.auto_heal_setting") { @@ -713,7 +711,7 @@ func ExpandSiteConfigLinuxWebAppSlot(siteConfig []SiteConfigLinuxWebAppSlot, exi } if metadata.ResourceData.HasChange("site_config.0.vnet_route_all_enabled") { - expanded.VnetRouteAllEnabled = utils.Bool(linuxSlotSiteConfig.VnetRouteAllEnabled) + expanded.VnetRouteAllEnabled = pointer.To(linuxSlotSiteConfig.VnetRouteAllEnabled) } return expanded, nil @@ -725,33 +723,33 @@ func FlattenSiteConfigLinuxWebAppSlot(appSiteSlotConfig *web.SiteConfig, healthC } siteConfig := SiteConfigLinuxWebAppSlot{ - AlwaysOn: utils.NormaliseNilableBool(appSiteSlotConfig.AlwaysOn), - AppCommandLine: utils.NormalizeNilableString(appSiteSlotConfig.AppCommandLine), - AutoHeal: utils.NormaliseNilableBool(appSiteSlotConfig.AutoHealEnabled), + AlwaysOn: pointer.From(appSiteSlotConfig.AlwaysOn), + AppCommandLine: pointer.From(appSiteSlotConfig.AppCommandLine), + AutoHeal: pointer.From(appSiteSlotConfig.AutoHealEnabled), AutoHealSettings: flattenAutoHealSettingsLinux(appSiteSlotConfig.AutoHealRules), - AutoSwapSlotName: utils.NormalizeNilableString(appSiteSlotConfig.AutoSwapSlotName), - ContainerRegistryMSI: utils.NormalizeNilableString(appSiteSlotConfig.AcrUserManagedIdentityID), - DetailedErrorLogging: utils.NormaliseNilableBool(appSiteSlotConfig.DetailedErrorLoggingEnabled), - Http2Enabled: utils.NormaliseNilableBool(appSiteSlotConfig.HTTP20Enabled), + AutoSwapSlotName: pointer.From(appSiteSlotConfig.AutoSwapSlotName), + ContainerRegistryMSI: pointer.From(appSiteSlotConfig.AcrUserManagedIdentityID), + DetailedErrorLogging: pointer.From(appSiteSlotConfig.DetailedErrorLoggingEnabled), + Http2Enabled: pointer.From(appSiteSlotConfig.HTTP20Enabled), IpRestriction: FlattenIpRestrictions(appSiteSlotConfig.IPSecurityRestrictions), ManagedPipelineMode: string(appSiteSlotConfig.ManagedPipelineMode), ScmType: string(appSiteSlotConfig.ScmType), FtpsState: string(appSiteSlotConfig.FtpsState), - HealthCheckPath: utils.NormalizeNilableString(appSiteSlotConfig.HealthCheckPath), - HealthCheckEvictionTime: utils.NormaliseNilableInt(healthCheckCount), + HealthCheckPath: pointer.From(appSiteSlotConfig.HealthCheckPath), + HealthCheckEvictionTime: pointer.From(healthCheckCount), LoadBalancing: string(appSiteSlotConfig.LoadBalancing), - LocalMysql: utils.NormaliseNilableBool(appSiteSlotConfig.LocalMySQLEnabled), + LocalMysql: pointer.From(appSiteSlotConfig.LocalMySQLEnabled), MinTlsVersion: string(appSiteSlotConfig.MinTLSVersion), - WorkerCount: int(utils.NormaliseNilableInt32(appSiteSlotConfig.NumberOfWorkers)), - RemoteDebugging: utils.NormaliseNilableBool(appSiteSlotConfig.RemoteDebuggingEnabled), - RemoteDebuggingVersion: strings.ToUpper(utils.NormalizeNilableString(appSiteSlotConfig.RemoteDebuggingVersion)), + WorkerCount: int(pointer.From(appSiteSlotConfig.NumberOfWorkers)), + RemoteDebugging: pointer.From(appSiteSlotConfig.RemoteDebuggingEnabled), + RemoteDebuggingVersion: strings.ToUpper(pointer.From(appSiteSlotConfig.RemoteDebuggingVersion)), ScmIpRestriction: FlattenIpRestrictions(appSiteSlotConfig.ScmIPSecurityRestrictions), ScmMinTlsVersion: string(appSiteSlotConfig.ScmMinTLSVersion), - ScmUseMainIpRestriction: utils.NormaliseNilableBool(appSiteSlotConfig.ScmIPSecurityRestrictionsUseMain), - Use32BitWorker: utils.NormaliseNilableBool(appSiteSlotConfig.Use32BitWorkerProcess), - UseManagedIdentityACR: utils.NormaliseNilableBool(appSiteSlotConfig.AcrUseManagedIdentityCreds), - WebSockets: utils.NormaliseNilableBool(appSiteSlotConfig.WebSocketsEnabled), - VnetRouteAllEnabled: utils.NormaliseNilableBool(appSiteSlotConfig.VnetRouteAllEnabled), + ScmUseMainIpRestriction: pointer.From(appSiteSlotConfig.ScmIPSecurityRestrictionsUseMain), + Use32BitWorker: pointer.From(appSiteSlotConfig.Use32BitWorkerProcess), + UseManagedIdentityACR: pointer.From(appSiteSlotConfig.AcrUseManagedIdentityCreds), + WebSockets: pointer.From(appSiteSlotConfig.WebSocketsEnabled), + VnetRouteAllEnabled: pointer.From(appSiteSlotConfig.VnetRouteAllEnabled), } if appSiteSlotConfig.APIManagementConfig != nil && appSiteSlotConfig.APIManagementConfig.ID != nil { @@ -775,16 +773,24 @@ func FlattenSiteConfigLinuxWebAppSlot(appSiteSlotConfig *web.SiteConfig, healthC } if appSiteSlotConfig.Cors != nil { + corsEmpty := false corsSettings := appSiteSlotConfig.Cors cors := CorsSetting{} if corsSettings.SupportCredentials != nil { cors.SupportCredentials = *corsSettings.SupportCredentials } - if corsSettings.AllowedOrigins != nil && len(*corsSettings.AllowedOrigins) != 0 { - cors.AllowedOrigins = *corsSettings.AllowedOrigins + if corsSettings.AllowedOrigins != nil { + if len(*corsSettings.AllowedOrigins) > 0 { + cors.AllowedOrigins = *corsSettings.AllowedOrigins + } else if !cors.SupportCredentials { + corsEmpty = true + } + } + + if !corsEmpty { + siteConfig.Cors = []CorsSetting{cors} } - siteConfig.Cors = []CorsSetting{cors} } return []SiteConfigLinuxWebAppSlot{siteConfig} @@ -810,45 +816,93 @@ func ExpandSiteConfigWindowsWebAppSlot(siteConfig []SiteConfigWindowsWebAppSlot, } if metadata.ResourceData.HasChange("site_config.0.always_on") { - expanded.AlwaysOn = utils.Bool(winSlotSiteConfig.AlwaysOn) + expanded.AlwaysOn = pointer.To(winSlotSiteConfig.AlwaysOn) } if metadata.ResourceData.HasChange("site_config.0.api_management_api_id") { expanded.APIManagementConfig = &web.APIManagementConfig{ - ID: utils.String(winSlotSiteConfig.ApiManagementConfigId), + ID: pointer.To(winSlotSiteConfig.ApiManagementConfigId), } } if metadata.ResourceData.HasChange("site_config.0.api_definition_url") { expanded.APIDefinition = &web.APIDefinitionInfo{ - URL: utils.String(winSlotSiteConfig.ApiDefinition), + URL: pointer.To(winSlotSiteConfig.ApiDefinition), } } if metadata.ResourceData.HasChange("site_config.0.app_command_line") { - expanded.AppCommandLine = utils.String(winSlotSiteConfig.AppCommandLine) + expanded.AppCommandLine = pointer.To(winSlotSiteConfig.AppCommandLine) } if metadata.ResourceData.HasChange("site_config.0.application_stack") { if len(winSlotSiteConfig.ApplicationStack) == 1 { winAppStack := winSlotSiteConfig.ApplicationStack[0] - expanded.NetFrameworkVersion = utils.String(winAppStack.NetFrameworkVersion) - expanded.PhpVersion = utils.String(winAppStack.PhpVersion) - expanded.NodeVersion = utils.String(winAppStack.NodeVersion) - expanded.PythonVersion = utils.String(winAppStack.PythonVersion) - expanded.JavaVersion = utils.String(winAppStack.JavaVersion) - expanded.JavaContainer = utils.String(winAppStack.JavaContainer) - expanded.JavaContainerVersion = utils.String(winAppStack.JavaContainerVersion) - if winAppStack.DockerContainerName != "" { + + if winAppStack.NetFrameworkVersion != "" { + expanded.NetFrameworkVersion = pointer.To(winAppStack.NetFrameworkVersion) + if currentStack == "" { + currentStack = CurrentStackDotNet + } + } + if winAppStack.NetCoreVersion != "" { + expanded.NetFrameworkVersion = pointer.To(winAppStack.NetCoreVersion) + if currentStack == "" { + currentStack = CurrentStackDotNetCore + } + } + if winAppStack.NodeVersion != "" { + // Note: node version is now exclusively controlled via app_setting.WEBSITE_NODE_DEFAULT_VERSION + if currentStack == "" { + currentStack = CurrentStackNode + } + } + if winAppStack.PhpVersion != "" { + if winAppStack.PhpVersion != PhpVersionOff { + expanded.PhpVersion = pointer.To(winAppStack.PhpVersion) + } else { + expanded.PhpVersion = pointer.To("") + } + if currentStack == "" { + currentStack = CurrentStackPhp + } + } + if winAppStack.PythonVersion != "" || winAppStack.Python { + expanded.PythonVersion = pointer.To(winAppStack.PythonVersion) + if currentStack == "" { + currentStack = CurrentStackPython + } + } + if winAppStack.JavaVersion != "" { + expanded.JavaVersion = pointer.To(winAppStack.JavaVersion) + switch { + case winAppStack.JavaEmbeddedServer: + expanded.JavaContainer = pointer.To(JavaContainerEmbeddedServer) + expanded.JavaContainerVersion = pointer.To(JavaContainerEmbeddedServerVersion) + + case winAppStack.TomcatVersion != "": + expanded.JavaContainer = pointer.To(JavaContainerTomcat) + expanded.JavaContainerVersion = pointer.To(winAppStack.TomcatVersion) + + case winAppStack.JavaContainer != "": + expanded.JavaContainer = pointer.To(winAppStack.JavaContainer) + expanded.JavaContainerVersion = pointer.To(winAppStack.JavaContainerVersion) + } + + if currentStack == "" { + currentStack = CurrentStackJava + } + } + if winAppStack.DockerContainerName != "" || winAppStack.DockerContainerRegistry != "" || winAppStack.DockerContainerTag != "" { if winAppStack.DockerContainerRegistry != "" { - expanded.WindowsFxVersion = utils.String(fmt.Sprintf("DOCKER|%s/%s:%s", winAppStack.DockerContainerRegistry, winAppStack.DockerContainerName, winAppStack.DockerContainerTag)) + expanded.WindowsFxVersion = pointer.To(fmt.Sprintf("DOCKER|%s/%s:%s", winAppStack.DockerContainerRegistry, winAppStack.DockerContainerName, winAppStack.DockerContainerTag)) } else { - expanded.WindowsFxVersion = utils.String(fmt.Sprintf("DOCKER|%s:%s", winAppStack.DockerContainerName, winAppStack.DockerContainerTag)) + expanded.WindowsFxVersion = pointer.To(fmt.Sprintf("DOCKER|%s:%s", winAppStack.DockerContainerName, winAppStack.DockerContainerTag)) } } - currentStack = winAppStack.CurrentStack + } else { - expanded.WindowsFxVersion = utils.String("") + expanded.WindowsFxVersion = pointer.To("") } } @@ -859,11 +913,11 @@ func ExpandSiteConfigWindowsWebAppSlot(siteConfig []SiteConfigWindowsWebAppSlot, } if metadata.ResourceData.HasChange("site_config.0.container_registry_use_managed_identity") { - expanded.AcrUseManagedIdentityCreds = utils.Bool(winSlotSiteConfig.UseManagedIdentityACR) + expanded.AcrUseManagedIdentityCreds = pointer.To(winSlotSiteConfig.UseManagedIdentityACR) } if metadata.ResourceData.HasChange("site_config.0.container_registry_managed_identity_client_id") { - expanded.AcrUserManagedIdentityID = utils.String(winSlotSiteConfig.ContainerRegistryUserMSI) + expanded.AcrUserManagedIdentityID = pointer.To(winSlotSiteConfig.ContainerRegistryUserMSI) } if metadata.ResourceData.HasChange("site_config.0.default_documents") { @@ -871,7 +925,7 @@ func ExpandSiteConfigWindowsWebAppSlot(siteConfig []SiteConfigWindowsWebAppSlot, } if metadata.ResourceData.HasChange("site_config.0.http2_enabled") { - expanded.HTTP20Enabled = utils.Bool(winSlotSiteConfig.Http2Enabled) + expanded.HTTP20Enabled = pointer.To(winSlotSiteConfig.Http2Enabled) } if metadata.ResourceData.HasChange("site_config.0.ip_restriction") { @@ -883,7 +937,7 @@ func ExpandSiteConfigWindowsWebAppSlot(siteConfig []SiteConfigWindowsWebAppSlot, } if metadata.ResourceData.HasChange("site_config.0.scm_use_main_ip_restriction") { - expanded.ScmIPSecurityRestrictionsUseMain = utils.Bool(winSlotSiteConfig.ScmUseMainIpRestriction) + expanded.ScmIPSecurityRestrictionsUseMain = pointer.To(winSlotSiteConfig.ScmUseMainIpRestriction) } if metadata.ResourceData.HasChange("site_config.0.scm_ip_restriction") { @@ -895,7 +949,7 @@ func ExpandSiteConfigWindowsWebAppSlot(siteConfig []SiteConfigWindowsWebAppSlot, } if metadata.ResourceData.HasChange("site_config.0.local_mysql_enabled") { - expanded.LocalMySQLEnabled = utils.Bool(winSlotSiteConfig.LocalMysql) + expanded.LocalMySQLEnabled = pointer.To(winSlotSiteConfig.LocalMysql) } if metadata.ResourceData.HasChange("site_config.0.load_balancing_mode") { @@ -907,19 +961,19 @@ func ExpandSiteConfigWindowsWebAppSlot(siteConfig []SiteConfigWindowsWebAppSlot, } if metadata.ResourceData.HasChange("site_config.0.remote_debugging_enabled") { - expanded.RemoteDebuggingEnabled = utils.Bool(winSlotSiteConfig.RemoteDebugging) + expanded.RemoteDebuggingEnabled = pointer.To(winSlotSiteConfig.RemoteDebugging) } if metadata.ResourceData.HasChange("site_config.0.remote_debugging_version") { - expanded.RemoteDebuggingVersion = utils.String(winSlotSiteConfig.RemoteDebuggingVersion) + expanded.RemoteDebuggingVersion = pointer.To(winSlotSiteConfig.RemoteDebuggingVersion) } if metadata.ResourceData.HasChange("site_config.0.use_32_bit_worker") { - expanded.Use32BitWorkerProcess = utils.Bool(winSlotSiteConfig.Use32BitWorker) + expanded.Use32BitWorkerProcess = pointer.To(winSlotSiteConfig.Use32BitWorker) } if metadata.ResourceData.HasChange("site_config.0.websockets_enabled") { - expanded.WebSocketsEnabled = utils.Bool(winSlotSiteConfig.WebSockets) + expanded.WebSocketsEnabled = pointer.To(winSlotSiteConfig.WebSockets) } if metadata.ResourceData.HasChange("site_config.0.ftps_state") { @@ -927,11 +981,11 @@ func ExpandSiteConfigWindowsWebAppSlot(siteConfig []SiteConfigWindowsWebAppSlot, } if metadata.ResourceData.HasChange("site_config.0.health_check_path") { - expanded.HealthCheckPath = utils.String(winSlotSiteConfig.HealthCheckPath) + expanded.HealthCheckPath = pointer.To(winSlotSiteConfig.HealthCheckPath) } if metadata.ResourceData.HasChange("site_config.0.worker_count") { - expanded.NumberOfWorkers = utils.Int32(int32(winSlotSiteConfig.WorkerCount)) + expanded.NumberOfWorkers = pointer.To(int32(winSlotSiteConfig.WorkerCount)) } if metadata.ResourceData.HasChange("site_config.0.minimum_tls_version") { @@ -943,7 +997,7 @@ func ExpandSiteConfigWindowsWebAppSlot(siteConfig []SiteConfigWindowsWebAppSlot, } if metadata.ResourceData.HasChange("site_config.0.auto_swap_slot_name") { - expanded.AutoSwapSlotName = utils.String(winSlotSiteConfig.AutoSwapSlotName) + expanded.AutoSwapSlotName = pointer.To(winSlotSiteConfig.AutoSwapSlotName) } if metadata.ResourceData.HasChange("site_config.0.cors") { @@ -957,7 +1011,7 @@ func ExpandSiteConfigWindowsWebAppSlot(siteConfig []SiteConfigWindowsWebAppSlot, } if metadata.ResourceData.HasChange("site_config.0.auto_heal_enabled") { - expanded.AutoHealEnabled = utils.Bool(winSlotSiteConfig.AutoHeal) + expanded.AutoHealEnabled = pointer.To(winSlotSiteConfig.AutoHeal) } if metadata.ResourceData.HasChange("site_config.0.auto_heal_setting") { @@ -965,7 +1019,7 @@ func ExpandSiteConfigWindowsWebAppSlot(siteConfig []SiteConfigWindowsWebAppSlot, } if metadata.ResourceData.HasChange("site_config.0.vnet_route_all_enabled") { - expanded.VnetRouteAllEnabled = utils.Bool(winSlotSiteConfig.VnetRouteAllEnabled) + expanded.VnetRouteAllEnabled = pointer.To(winSlotSiteConfig.VnetRouteAllEnabled) } return expanded, ¤tStack, nil @@ -977,34 +1031,34 @@ func FlattenSiteConfigWindowsAppSlot(appSiteSlotConfig *web.SiteConfig, currentS } siteConfig := SiteConfigWindowsWebAppSlot{ - AlwaysOn: utils.NormaliseNilableBool(appSiteSlotConfig.AlwaysOn), - AppCommandLine: utils.NormalizeNilableString(appSiteSlotConfig.AppCommandLine), - AutoHeal: utils.NormaliseNilableBool(appSiteSlotConfig.AutoHealEnabled), + AlwaysOn: pointer.From(appSiteSlotConfig.AlwaysOn), + AppCommandLine: pointer.From(appSiteSlotConfig.AppCommandLine), + AutoHeal: pointer.From(appSiteSlotConfig.AutoHealEnabled), AutoHealSettings: flattenAutoHealSettingsWindows(appSiteSlotConfig.AutoHealRules), - AutoSwapSlotName: utils.NormalizeNilableString(appSiteSlotConfig.AutoSwapSlotName), - ContainerRegistryUserMSI: utils.NormalizeNilableString(appSiteSlotConfig.AcrUserManagedIdentityID), - DetailedErrorLogging: utils.NormaliseNilableBool(appSiteSlotConfig.DetailedErrorLoggingEnabled), + AutoSwapSlotName: pointer.From(appSiteSlotConfig.AutoSwapSlotName), + ContainerRegistryUserMSI: pointer.From(appSiteSlotConfig.AcrUserManagedIdentityID), + DetailedErrorLogging: pointer.From(appSiteSlotConfig.DetailedErrorLoggingEnabled), FtpsState: string(appSiteSlotConfig.FtpsState), - HealthCheckPath: utils.NormalizeNilableString(appSiteSlotConfig.HealthCheckPath), - HealthCheckEvictionTime: utils.NormaliseNilableInt(healthCheckCount), - Http2Enabled: utils.NormaliseNilableBool(appSiteSlotConfig.HTTP20Enabled), + HealthCheckPath: pointer.From(appSiteSlotConfig.HealthCheckPath), + HealthCheckEvictionTime: pointer.From(healthCheckCount), + Http2Enabled: pointer.From(appSiteSlotConfig.HTTP20Enabled), IpRestriction: FlattenIpRestrictions(appSiteSlotConfig.IPSecurityRestrictions), LoadBalancing: string(appSiteSlotConfig.LoadBalancing), - LocalMysql: utils.NormaliseNilableBool(appSiteSlotConfig.LocalMySQLEnabled), + LocalMysql: pointer.From(appSiteSlotConfig.LocalMySQLEnabled), ManagedPipelineMode: string(appSiteSlotConfig.ManagedPipelineMode), MinTlsVersion: string(appSiteSlotConfig.MinTLSVersion), - WorkerCount: int(utils.NormaliseNilableInt32(appSiteSlotConfig.NumberOfWorkers)), - RemoteDebugging: utils.NormaliseNilableBool(appSiteSlotConfig.RemoteDebuggingEnabled), - RemoteDebuggingVersion: strings.ToUpper(utils.NormalizeNilableString(appSiteSlotConfig.RemoteDebuggingVersion)), + WorkerCount: int(pointer.From(appSiteSlotConfig.NumberOfWorkers)), + RemoteDebugging: pointer.From(appSiteSlotConfig.RemoteDebuggingEnabled), + RemoteDebuggingVersion: strings.ToUpper(pointer.From(appSiteSlotConfig.RemoteDebuggingVersion)), ScmIpRestriction: FlattenIpRestrictions(appSiteSlotConfig.ScmIPSecurityRestrictions), ScmMinTlsVersion: string(appSiteSlotConfig.ScmMinTLSVersion), ScmType: string(appSiteSlotConfig.ScmType), - ScmUseMainIpRestriction: utils.NormaliseNilableBool(appSiteSlotConfig.ScmIPSecurityRestrictionsUseMain), - Use32BitWorker: utils.NormaliseNilableBool(appSiteSlotConfig.Use32BitWorkerProcess), - UseManagedIdentityACR: utils.NormaliseNilableBool(appSiteSlotConfig.AcrUseManagedIdentityCreds), + ScmUseMainIpRestriction: pointer.From(appSiteSlotConfig.ScmIPSecurityRestrictionsUseMain), + Use32BitWorker: pointer.From(appSiteSlotConfig.Use32BitWorkerProcess), + UseManagedIdentityACR: pointer.From(appSiteSlotConfig.AcrUseManagedIdentityCreds), VirtualApplications: flattenVirtualApplications(appSiteSlotConfig.VirtualApplications), - WebSockets: utils.NormaliseNilableBool(appSiteSlotConfig.WebSocketsEnabled), - VnetRouteAllEnabled: utils.NormaliseNilableBool(appSiteSlotConfig.VnetRouteAllEnabled), + WebSockets: pointer.From(appSiteSlotConfig.WebSocketsEnabled), + VnetRouteAllEnabled: pointer.From(appSiteSlotConfig.VnetRouteAllEnabled), } if appSiteSlotConfig.APIManagementConfig != nil && appSiteSlotConfig.APIManagementConfig.ID != nil { @@ -1023,28 +1077,39 @@ func FlattenSiteConfigWindowsAppSlot(appSiteSlotConfig *web.SiteConfig, currentS siteConfig.WorkerCount = int(*appSiteSlotConfig.NumberOfWorkers) } - winAppStack := ApplicationStackWindows{ - NetFrameworkVersion: utils.NormalizeNilableString(appSiteSlotConfig.NetFrameworkVersion), - PhpVersion: utils.NormalizeNilableString(appSiteSlotConfig.PhpVersion), - NodeVersion: utils.NormalizeNilableString(appSiteSlotConfig.NodeVersion), - PythonVersion: utils.NormalizeNilableString(appSiteSlotConfig.PythonVersion), - JavaVersion: utils.NormalizeNilableString(appSiteSlotConfig.JavaVersion), - JavaContainer: utils.NormalizeNilableString(appSiteSlotConfig.JavaContainer), - JavaContainerVersion: utils.NormalizeNilableString(appSiteSlotConfig.JavaContainerVersion), + winAppStack := ApplicationStackWindows{} + + winAppStack.NetFrameworkVersion = pointer.From(appSiteSlotConfig.NetFrameworkVersion) + if currentStack == CurrentStackDotNetCore { + winAppStack.NetCoreVersion = pointer.From(appSiteSlotConfig.NetFrameworkVersion) + } + winAppStack.PhpVersion = pointer.From(appSiteSlotConfig.PhpVersion) + if winAppStack.PhpVersion == "" { + winAppStack.PhpVersion = PhpVersionOff + } + winAppStack.PythonVersion = pointer.From(appSiteSlotConfig.PythonVersion) // This _should_ always be `""` + winAppStack.Python = currentStack == CurrentStackPython + winAppStack.JavaVersion = pointer.From(appSiteSlotConfig.JavaVersion) + switch pointer.From(appSiteSlotConfig.JavaContainer) { + case JavaContainerTomcat: + winAppStack.TomcatVersion = *appSiteSlotConfig.JavaContainerVersion + case JavaContainerEmbeddedServer: + winAppStack.JavaEmbeddedServer = true } - siteConfig.WindowsFxVersion = utils.NormalizeNilableString(appSiteSlotConfig.WindowsFxVersion) + siteConfig.WindowsFxVersion = pointer.From(appSiteSlotConfig.WindowsFxVersion) if siteConfig.WindowsFxVersion != "" { // Decode the string to docker values parts := strings.Split(strings.TrimPrefix(siteConfig.WindowsFxVersion, "DOCKER|"), ":") if len(parts) == 2 { winAppStack.DockerContainerTag = parts[1] path := strings.Split(parts[0], "/") - if len(path) > 2 { + if len(path) > 1 { winAppStack.DockerContainerRegistry = path[0] winAppStack.DockerContainerName = strings.TrimPrefix(parts[0], fmt.Sprintf("%s/", path[0])) + } else { + winAppStack.DockerContainerName = path[0] } - winAppStack.DockerContainerName = path[0] } } winAppStack.CurrentStack = currentStack @@ -1052,16 +1117,23 @@ func FlattenSiteConfigWindowsAppSlot(appSiteSlotConfig *web.SiteConfig, currentS siteConfig.ApplicationStack = []ApplicationStackWindows{winAppStack} if appSiteSlotConfig.Cors != nil { - cors := CorsSetting{} + corsEmpty := false corsSettings := appSiteSlotConfig.Cors + cors := CorsSetting{} if corsSettings.SupportCredentials != nil { cors.SupportCredentials = *corsSettings.SupportCredentials } - if corsSettings.AllowedOrigins != nil && len(*corsSettings.AllowedOrigins) != 0 { - cors.AllowedOrigins = *corsSettings.AllowedOrigins + if corsSettings.AllowedOrigins != nil { + if len(*corsSettings.AllowedOrigins) > 0 { + cors.AllowedOrigins = *corsSettings.AllowedOrigins + } else if !cors.SupportCredentials { + corsEmpty = true + } + } + if !corsEmpty { + siteConfig.Cors = []CorsSetting{cors} } - siteConfig.Cors = []CorsSetting{cors} } return []SiteConfigWindowsWebAppSlot{siteConfig} diff --git a/internal/services/appservice/helpers/windows_web_app_schema.go b/internal/services/appservice/helpers/windows_web_app_schema.go new file mode 100644 index 000000000000..48b0d423e5a3 --- /dev/null +++ b/internal/services/appservice/helpers/windows_web_app_schema.go @@ -0,0 +1,773 @@ +package helpers + +import ( + "fmt" + "strings" + + "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2021-02-01/web" // nolint: staticcheck + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/apimanagement/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/apimanagement/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +type SiteConfigWindows struct { + AlwaysOn bool `tfschema:"always_on"` + ApiManagementConfigId string `tfschema:"api_management_api_id"` + ApiDefinition string `tfschema:"api_definition_url"` + AppCommandLine string `tfschema:"app_command_line"` + AutoHeal bool `tfschema:"auto_heal_enabled"` + AutoHealSettings []AutoHealSettingWindows `tfschema:"auto_heal_setting"` + UseManagedIdentityACR bool `tfschema:"container_registry_use_managed_identity"` + ContainerRegistryUserMSI string `tfschema:"container_registry_managed_identity_client_id"` + DefaultDocuments []string `tfschema:"default_documents"` + Http2Enabled bool `tfschema:"http2_enabled"` + IpRestriction []IpRestriction `tfschema:"ip_restriction"` + ScmUseMainIpRestriction bool `tfschema:"scm_use_main_ip_restriction"` + ScmIpRestriction []IpRestriction `tfschema:"scm_ip_restriction"` + LoadBalancing string `tfschema:"load_balancing_mode"` + LocalMysql bool `tfschema:"local_mysql_enabled"` + ManagedPipelineMode string `tfschema:"managed_pipeline_mode"` + RemoteDebugging bool `tfschema:"remote_debugging_enabled"` + RemoteDebuggingVersion string `tfschema:"remote_debugging_version"` + ScmType string `tfschema:"scm_type"` + 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"` + WorkerCount int `tfschema:"worker_count"` + ApplicationStack []ApplicationStackWindows `tfschema:"application_stack"` + VirtualApplications []VirtualApplication `tfschema:"virtual_application"` + MinTlsVersion string `tfschema:"minimum_tls_version"` + ScmMinTlsVersion string `tfschema:"scm_minimum_tls_version"` + Cors []CorsSetting `tfschema:"cors"` + DetailedErrorLogging bool `tfschema:"detailed_error_logging_enabled"` + WindowsFxVersion string `tfschema:"windows_fx_version"` + VnetRouteAllEnabled bool `tfschema:"vnet_route_all_enabled"` + // TODO new properties / blocks + // SiteLimits []SiteLimitsSettings `tfschema:"site_limits"` // TODO - ASE related for limiting App resource consumption + // PushSettings - Supported in SDK, but blocked by manual step needed for connecting app to notification hub. +} + +func SiteConfigSchemaWindows() *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, + Default: true, + }, + + "api_management_api_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validate.ApiID, + }, + + "api_definition_url": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.IsURLWithHTTPorHTTPS, + }, + + "application_stack": windowsApplicationStackSchema(), + + "app_command_line": { + Type: pluginsdk.TypeString, + Optional: true, + }, + + "auto_heal_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + RequiredWith: []string{ + "site_config.0.auto_heal_setting", + }, + }, + + "auto_heal_setting": autoHealSettingSchemaWindows(), + + "container_registry_use_managed_identity": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "container_registry_managed_identity_client_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.IsUUID, + }, + + "default_documents": { + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "http2_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "ip_restriction": IpRestrictionSchema(), + + "scm_use_main_ip_restriction": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "scm_ip_restriction": IpRestrictionSchema(), + + "local_mysql_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "load_balancing_mode": { + Type: pluginsdk.TypeString, + Optional: true, + Default: string(web.SiteLoadBalancingLeastRequests), + ValidateFunc: validation.StringInSlice([]string{ + string(web.SiteLoadBalancingLeastRequests), + string(web.SiteLoadBalancingWeightedRoundRobin), + string(web.SiteLoadBalancingLeastResponseTime), + string(web.SiteLoadBalancingWeightedTotalTraffic), + string(web.SiteLoadBalancingRequestHash), + string(web.SiteLoadBalancingPerSiteRoundRobin), + }, false), + }, + + "managed_pipeline_mode": { + Type: pluginsdk.TypeString, + Optional: true, + Default: string(web.ManagedPipelineModeIntegrated), + ValidateFunc: validation.StringInSlice([]string{ + string(web.ManagedPipelineModeClassic), + string(web.ManagedPipelineModeIntegrated), + }, false), + }, + + "remote_debugging_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "remote_debugging_version": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + "VS2017", + "VS2019", + }, false), + }, + + "scm_type": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "use_32_bit_worker": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, // Variable default value depending on several factors, such as plan type? + }, + + "websockets_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: 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), + }, + + "health_check_path": { + Type: pluginsdk.TypeString, + Optional: true, + }, + + "health_check_eviction_time_in_min": { + 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`", + }, + + "worker_count": { + Type: pluginsdk.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: validation.IntBetween(1, 100), + }, + + "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), + }, + + "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), + }, + + "cors": CorsSettingsSchema(), + + "virtual_application": virtualApplicationsSchema(), + + "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_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "linux_fx_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "windows_fx_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + } +} + +func SiteConfigSchemaWindowsComputed() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "always_on": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "api_management_api_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "api_definition_url": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "application_stack": windowsApplicationStackSchemaComputed(), + + "app_command_line": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "auto_heal_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "auto_heal_setting": autoHealSettingSchemaWindowsComputed(), + + "container_registry_use_managed_identity": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "container_registry_managed_identity_client_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "default_documents": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "http2_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "ip_restriction": IpRestrictionSchemaComputed(), + + "scm_use_main_ip_restriction": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "scm_ip_restriction": IpRestrictionSchemaComputed(), + + "local_mysql_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "load_balancing_mode": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "managed_pipeline_mode": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "remote_debugging_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "remote_debugging_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "scm_type": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "use_32_bit_worker": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "websockets_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "ftps_state": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "health_check_path": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "health_check_eviction_time_in_min": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + + "worker_count": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + + "minimum_tls_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "scm_minimum_tls_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "cors": CorsSettingsSchemaComputed(), + + "virtual_application": virtualApplicationsSchemaComputed(), + + "detailed_error_logging_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + + "windows_fx_version": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "vnet_route_all_enabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + }, + }, + } +} + +func ExpandSiteConfigWindows(siteConfig []SiteConfigWindows, existing *web.SiteConfig, metadata sdk.ResourceMetaData, servicePlan web.AppServicePlan) (*web.SiteConfig, *string, error) { + if len(siteConfig) == 0 { + return nil, nil, nil + } + + expanded := &web.SiteConfig{} + if existing != nil { + expanded = existing + } + + winSiteConfig := siteConfig[0] + + currentStack := "" + if len(winSiteConfig.ApplicationStack) == 1 { + winAppStack := winSiteConfig.ApplicationStack[0] + currentStack = winAppStack.CurrentStack + } + + if servicePlan.Sku != nil && servicePlan.Sku.Name != nil { + if isFreeOrSharedServicePlan(*servicePlan.Sku.Name) { + if winSiteConfig.AlwaysOn { + return nil, nil, fmt.Errorf("always_on cannot be set to true when using Free, F1, D1 Sku") + } + if expanded.AlwaysOn != nil && *expanded.AlwaysOn { + // TODO - This code will not work as expected due to the service plan always being updated before the App, so the apply will always error out on the Service Plan change if `always_on` is incorrectly set. + // Need to investigate if there's a way to avoid users needing to run 2 applies for this. + return nil, nil, fmt.Errorf("always_on feature has to be turned off before switching to a free/shared Sku") + } + } + } + expanded.AlwaysOn = pointer.To(winSiteConfig.AlwaysOn) + + if metadata.ResourceData.HasChange("site_config.0.api_management_api_id") { + expanded.APIManagementConfig = &web.APIManagementConfig{ + ID: pointer.To(winSiteConfig.ApiManagementConfigId), + } + } + + if metadata.ResourceData.HasChange("site_config.0.api_definition_url") { + expanded.APIDefinition = &web.APIDefinitionInfo{ + URL: pointer.To(winSiteConfig.ApiDefinition), + } + } + + if metadata.ResourceData.HasChange("site_config.0.app_command_line") { + expanded.AppCommandLine = pointer.To(winSiteConfig.AppCommandLine) + } + + if metadata.ResourceData.HasChange("site_config.0.application_stack") { + if len(winSiteConfig.ApplicationStack) == 1 { + winAppStack := winSiteConfig.ApplicationStack[0] + if winAppStack.NetFrameworkVersion != "" { + expanded.NetFrameworkVersion = pointer.To(winAppStack.NetFrameworkVersion) + if currentStack == "" { + currentStack = CurrentStackDotNet + } + } + if winAppStack.NetCoreVersion != "" { + expanded.NetFrameworkVersion = pointer.To(winAppStack.NetCoreVersion) + if currentStack == "" { + currentStack = CurrentStackDotNetCore + } + } + if winAppStack.NodeVersion != "" { + // Note: node version is now exclusively controlled via app_setting.WEBSITE_NODE_DEFAULT_VERSION + if currentStack == "" { + currentStack = CurrentStackNode + } + } + if winAppStack.PhpVersion != "" { + if winAppStack.PhpVersion != PhpVersionOff { + expanded.PhpVersion = pointer.To(winAppStack.PhpVersion) + } else { + expanded.PhpVersion = pointer.To("") + } + if currentStack == "" { + currentStack = CurrentStackPhp + } + } + if winAppStack.PythonVersion != "" || winAppStack.Python { + expanded.PythonVersion = pointer.To(winAppStack.PythonVersion) + if currentStack == "" { + currentStack = CurrentStackPython + } + } + if winAppStack.JavaVersion != "" { + expanded.JavaVersion = pointer.To(winAppStack.JavaVersion) + switch { + case winAppStack.JavaEmbeddedServer: + expanded.JavaContainer = pointer.To(JavaContainerEmbeddedServer) + expanded.JavaContainerVersion = pointer.To(JavaContainerEmbeddedServerVersion) + case winAppStack.TomcatVersion != "": + expanded.JavaContainer = pointer.To(JavaContainerTomcat) + expanded.JavaContainerVersion = pointer.To(winAppStack.TomcatVersion) + case winAppStack.JavaContainer != "": + expanded.JavaContainer = pointer.To(winAppStack.JavaContainer) + expanded.JavaContainerVersion = pointer.To(winAppStack.JavaContainerVersion) + } + + if currentStack == "" { + currentStack = CurrentStackJava + } + } + if winAppStack.DockerContainerName != "" || winAppStack.DockerContainerRegistry != "" || winAppStack.DockerContainerTag != "" { + if winAppStack.DockerContainerRegistry != "" { + expanded.WindowsFxVersion = pointer.To(fmt.Sprintf("DOCKER|%s/%s:%s", winAppStack.DockerContainerRegistry, winAppStack.DockerContainerName, winAppStack.DockerContainerTag)) + } else { + expanded.WindowsFxVersion = pointer.To(fmt.Sprintf("DOCKER|%s:%s", winAppStack.DockerContainerName, winAppStack.DockerContainerTag)) + } + } + } else { + expanded.WindowsFxVersion = pointer.To("") + } + } + + if metadata.ResourceData.HasChange("site_config.0.virtual_application") { + expanded.VirtualApplications = expandVirtualApplicationsForUpdate(winSiteConfig.VirtualApplications) + } else { + expanded.VirtualApplications = expandVirtualApplications(winSiteConfig.VirtualApplications) + } + + expanded.AcrUseManagedIdentityCreds = pointer.To(winSiteConfig.UseManagedIdentityACR) + + if metadata.ResourceData.HasChange("site_config.0.container_registry_managed_identity_client_id") { + expanded.AcrUserManagedIdentityID = pointer.To(winSiteConfig.ContainerRegistryUserMSI) + } + + if metadata.ResourceData.HasChange("site_config.0.default_documents") { + expanded.DefaultDocuments = &winSiteConfig.DefaultDocuments + } + + expanded.HTTP20Enabled = pointer.To(winSiteConfig.Http2Enabled) + + if metadata.ResourceData.HasChange("site_config.0.ip_restriction") { + ipRestrictions, err := ExpandIpRestrictions(winSiteConfig.IpRestriction) + if err != nil { + return nil, nil, fmt.Errorf("expanding IP Restrictions: %+v", err) + } + expanded.IPSecurityRestrictions = ipRestrictions + } + + expanded.ScmIPSecurityRestrictionsUseMain = pointer.To(winSiteConfig.ScmUseMainIpRestriction) + + if metadata.ResourceData.HasChange("site_config.0.scm_ip_restriction") { + scmIpRestrictions, err := ExpandIpRestrictions(winSiteConfig.ScmIpRestriction) + if err != nil { + return nil, nil, fmt.Errorf("expanding SCM IP Restrictions: %+v", err) + } + expanded.ScmIPSecurityRestrictions = scmIpRestrictions + } + + expanded.LocalMySQLEnabled = pointer.To(winSiteConfig.LocalMysql) + + if metadata.ResourceData.HasChange("site_config.0.load_balancing_mode") { + expanded.LoadBalancing = web.SiteLoadBalancing(winSiteConfig.LoadBalancing) + } + + if metadata.ResourceData.HasChange("site_config.0.managed_pipeline_mode") { + expanded.ManagedPipelineMode = web.ManagedPipelineMode(winSiteConfig.ManagedPipelineMode) + } + + expanded.RemoteDebuggingEnabled = pointer.To(winSiteConfig.RemoteDebugging) + + if metadata.ResourceData.HasChange("site_config.0.remote_debugging_version") { + expanded.RemoteDebuggingVersion = pointer.To(winSiteConfig.RemoteDebuggingVersion) + } + + expanded.Use32BitWorkerProcess = pointer.To(winSiteConfig.Use32BitWorker) + + expanded.WebSocketsEnabled = pointer.To(winSiteConfig.WebSockets) + + if metadata.ResourceData.HasChange("site_config.0.ftps_state") { + expanded.FtpsState = web.FtpsState(winSiteConfig.FtpsState) + } + + if metadata.ResourceData.HasChange("site_config.0.health_check_path") { + expanded.HealthCheckPath = pointer.To(winSiteConfig.HealthCheckPath) + } + + if metadata.ResourceData.HasChange("site_config.0.worker_count") { + expanded.NumberOfWorkers = pointer.To(int32(winSiteConfig.WorkerCount)) + } + + if metadata.ResourceData.HasChange("site_config.0.minimum_tls_version") { + expanded.MinTLSVersion = web.SupportedTLSVersions(winSiteConfig.MinTlsVersion) + } + + if metadata.ResourceData.HasChange("site_config.0.scm_minimum_tls_version") { + expanded.ScmMinTLSVersion = web.SupportedTLSVersions(winSiteConfig.ScmMinTlsVersion) + } + + if metadata.ResourceData.HasChange("site_config.0.cors") { + cors := ExpandCorsSettings(winSiteConfig.Cors) + if cors == nil { + cors = &web.CorsSettings{ + AllowedOrigins: &[]string{}, + } + } + expanded.Cors = cors + } + + if metadata.ResourceData.HasChange("site_config.0.auto_heal_enabled") { + expanded.AutoHealEnabled = pointer.To(winSiteConfig.AutoHeal) + } + + if metadata.ResourceData.HasChange("site_config.0.auto_heal_setting") { + expanded.AutoHealRules = expandAutoHealSettingsWindows(winSiteConfig.AutoHealSettings) + } + + if metadata.ResourceData.HasChange("site_config.0.vnet_route_all_enabled") { + expanded.VnetRouteAllEnabled = pointer.To(winSiteConfig.VnetRouteAllEnabled) + } + + return expanded, ¤tStack, nil +} + +func FlattenSiteConfigWindows(appSiteConfig *web.SiteConfig, currentStack string, healthCheckCount *int) ([]SiteConfigWindows, error) { + if appSiteConfig == nil { + return nil, nil + } + + siteConfig := SiteConfigWindows{ + AlwaysOn: pointer.From(appSiteConfig.AlwaysOn), + AppCommandLine: pointer.From(appSiteConfig.AppCommandLine), + AutoHeal: pointer.From(appSiteConfig.AutoHealEnabled), + AutoHealSettings: flattenAutoHealSettingsWindows(appSiteConfig.AutoHealRules), + ContainerRegistryUserMSI: pointer.From(appSiteConfig.AcrUserManagedIdentityID), + DetailedErrorLogging: pointer.From(appSiteConfig.DetailedErrorLoggingEnabled), + FtpsState: string(appSiteConfig.FtpsState), + HealthCheckPath: pointer.From(appSiteConfig.HealthCheckPath), + HealthCheckEvictionTime: pointer.From(healthCheckCount), + Http2Enabled: pointer.From(appSiteConfig.HTTP20Enabled), + IpRestriction: FlattenIpRestrictions(appSiteConfig.IPSecurityRestrictions), + LoadBalancing: string(appSiteConfig.LoadBalancing), + LocalMysql: pointer.From(appSiteConfig.LocalMySQLEnabled), + ManagedPipelineMode: string(appSiteConfig.ManagedPipelineMode), + MinTlsVersion: string(appSiteConfig.MinTLSVersion), + WorkerCount: int(pointer.From(appSiteConfig.NumberOfWorkers)), + RemoteDebugging: pointer.From(appSiteConfig.RemoteDebuggingEnabled), + RemoteDebuggingVersion: strings.ToUpper(pointer.From(appSiteConfig.RemoteDebuggingVersion)), + ScmIpRestriction: FlattenIpRestrictions(appSiteConfig.ScmIPSecurityRestrictions), + ScmMinTlsVersion: string(appSiteConfig.ScmMinTLSVersion), + ScmType: string(appSiteConfig.ScmType), + ScmUseMainIpRestriction: pointer.From(appSiteConfig.ScmIPSecurityRestrictionsUseMain), + Use32BitWorker: pointer.From(appSiteConfig.Use32BitWorkerProcess), + UseManagedIdentityACR: pointer.From(appSiteConfig.AcrUseManagedIdentityCreds), + VirtualApplications: flattenVirtualApplications(appSiteConfig.VirtualApplications), + WebSockets: pointer.From(appSiteConfig.WebSocketsEnabled), + VnetRouteAllEnabled: pointer.From(appSiteConfig.VnetRouteAllEnabled), + } + + if appSiteConfig.APIManagementConfig != nil && appSiteConfig.APIManagementConfig.ID != nil { + apiId, err := parse.ApiIDInsensitively(*appSiteConfig.APIManagementConfig.ID) + if err != nil { + return nil, fmt.Errorf("could not parse API Management ID: %+v", err) + } + siteConfig.ApiManagementConfigId = apiId.ID() + } + + if appSiteConfig.APIDefinition != nil && appSiteConfig.APIDefinition.URL != nil { + siteConfig.ApiDefinition = *appSiteConfig.APIDefinition.URL + } + + if appSiteConfig.DefaultDocuments != nil { + siteConfig.DefaultDocuments = *appSiteConfig.DefaultDocuments + } + + if appSiteConfig.NumberOfWorkers != nil { + siteConfig.WorkerCount = int(*appSiteConfig.NumberOfWorkers) + } + + var winAppStack ApplicationStackWindows + if currentStack == CurrentStackDotNetCore { + winAppStack.NetCoreVersion = pointer.From(appSiteConfig.NetFrameworkVersion) + } + if currentStack == CurrentStackDotNet { + winAppStack.NetFrameworkVersion = pointer.From(appSiteConfig.NetFrameworkVersion) + } + winAppStack.PhpVersion = pointer.From(appSiteConfig.PhpVersion) + if winAppStack.PhpVersion == "" { + winAppStack.PhpVersion = PhpVersionOff + } + winAppStack.PythonVersion = pointer.From(appSiteConfig.PythonVersion) // This _should_ always be `""` + winAppStack.Python = currentStack == CurrentStackPython + winAppStack.JavaVersion = pointer.From(appSiteConfig.JavaVersion) + switch pointer.From(appSiteConfig.JavaContainer) { + case JavaContainerTomcat: + winAppStack.TomcatVersion = *appSiteConfig.JavaContainerVersion + case JavaContainerEmbeddedServer: + winAppStack.JavaEmbeddedServer = true + } + + siteConfig.WindowsFxVersion = pointer.From(appSiteConfig.WindowsFxVersion) + if siteConfig.WindowsFxVersion != "" { + // Decode the string to docker values + parts := strings.Split(strings.TrimPrefix(siteConfig.WindowsFxVersion, "DOCKER|"), ":") + if len(parts) == 2 { + winAppStack.DockerContainerTag = parts[1] + path := strings.Split(parts[0], "/") + if len(path) > 1 { + winAppStack.DockerContainerRegistry = path[0] + winAppStack.DockerContainerName = strings.TrimPrefix(parts[0], fmt.Sprintf("%s/", path[0])) + } else { + winAppStack.DockerContainerName = path[0] + } + } + } + winAppStack.CurrentStack = currentStack + + siteConfig.ApplicationStack = []ApplicationStackWindows{winAppStack} + + if appSiteConfig.Cors != nil { + corsEmpty := false + corsSettings := appSiteConfig.Cors + cors := CorsSetting{} + if corsSettings.SupportCredentials != nil { + cors.SupportCredentials = *corsSettings.SupportCredentials + } + + if corsSettings.AllowedOrigins != nil { + if len(*corsSettings.AllowedOrigins) > 0 { + cors.AllowedOrigins = *corsSettings.AllowedOrigins + } else if !cors.SupportCredentials { + corsEmpty = true + } + } + if !corsEmpty { + siteConfig.Cors = []CorsSetting{cors} + } + } + + return []SiteConfigWindows{siteConfig}, nil +} diff --git a/internal/services/appservice/linux_function_app_data_source.go b/internal/services/appservice/linux_function_app_data_source.go index f05502152a45..8cc9f41b731c 100644 --- a/internal/services/appservice/linux_function_app_data_source.go +++ b/internal/services/appservice/linux_function_app_data_source.go @@ -393,7 +393,7 @@ func (m *LinuxFunctionAppDataSourceModel) unpackLinuxFunctionAppSettings(input w 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 LinuxFxVersion so can discard it here + case "WEBSITE_NODE_DEFAULT_VERSION": // Note - This is no longer used in Linux Apps case "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": if _, ok := metadata.ResourceData.GetOk("app_settings.WEBSITE_CONTENTAZUREFILECONNECTIONSTRING"); ok { appSettings[k] = utils.NormalizeNilableString(v) diff --git a/internal/services/appservice/linux_function_app_resource.go b/internal/services/appservice/linux_function_app_resource.go index 39ac6c7dbc82..972a9e5e3b95 100644 --- a/internal/services/appservice/linux_function_app_resource.go +++ b/internal/services/appservice/linux_function_app_resource.go @@ -499,7 +499,10 @@ func (r LinuxFunctionAppResource) Create() sdk.ResourceFunc { } } - backupConfig := helpers.ExpandBackupConfig(functionApp.Backup) + backupConfig, err := helpers.ExpandBackupConfig(functionApp.Backup) + if err != nil { + return fmt.Errorf("expanding backup configuration for Linux %s: %+v", id, err) + } if backupConfig.BackupRequestProperties != nil { if _, err := client.UpdateBackupConfiguration(ctx, id.ResourceGroup, id.SiteName, *backupConfig); err != nil { return fmt.Errorf("adding Backup Settings for Linux %s: %+v", id, err) @@ -903,7 +906,11 @@ func (r LinuxFunctionAppResource) Update() sdk.ResourceFunc { } if metadata.ResourceData.HasChange("backup") { - backupUpdate := helpers.ExpandBackupConfig(state.Backup) + backupUpdate, err := helpers.ExpandBackupConfig(state.Backup) + if err != nil { + return fmt.Errorf("expanding backup configuration for Linux %s: %+v", *id, err) + } + if backupUpdate.BackupRequestProperties == nil { if _, err := client.DeleteBackupConfiguration(ctx, id.ResourceGroup, id.SiteName); err != nil { return fmt.Errorf("removing Backup Settings for Linux %s: %+v", id, err) diff --git a/internal/services/appservice/linux_function_app_resource_test.go b/internal/services/appservice/linux_function_app_resource_test.go index ea6acb591b7d..e080932ea600 100644 --- a/internal/services/appservice/linux_function_app_resource_test.go +++ b/internal/services/appservice/linux_function_app_resource_test.go @@ -870,6 +870,7 @@ func TestAccLinuxFunctionApp_appStackDotNet31(t *testing.T) { Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("DOTNET|3.1"), ), }, data.ImportStep(), @@ -902,6 +903,7 @@ func TestAccLinuxFunctionApp_appStackDotNet6(t *testing.T) { Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("DOTNET|6.0"), ), }, data.ImportStep(), @@ -931,10 +933,11 @@ func TestAccLinuxFunctionApp_appStackPython(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.appStackPython(data, SkuBasicPlan, "3.7"), + Config: r.appStackPython(data, SkuBasicPlan, "3.9"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("PYTHON|3.9"), ), }, data.ImportStep(), @@ -947,18 +950,20 @@ func TestAccLinuxFunctionApp_appStackPythonUpdate(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.appStackPython(data, SkuBasicPlan, "3.7"), + Config: r.appStackPython(data, SkuBasicPlan, "3.9"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("PYTHON|3.9"), ), }, data.ImportStep(), { - Config: r.appStackPython(data, SkuBasicPlan, "3.9"), + Config: r.appStackPython(data, SkuBasicPlan, "3.10"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("PYTHON|3.10"), ), }, data.ImportStep(), @@ -975,6 +980,7 @@ func TestAccLinuxFunctionApp_appStackNode(t *testing.T) { Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("NODE|14"), ), }, data.ImportStep(), @@ -987,20 +993,20 @@ func TestAccLinuxFunctionApp_appStackNodeUpdate(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.appStackNode(data, SkuBasicPlan, "12"), + Config: r.appStackNode(data, SkuBasicPlan, "16"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), - check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("NODE|12"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("NODE|16"), ), }, data.ImportStep(), { - Config: r.appStackNode(data, SkuBasicPlan, "16"), + Config: r.appStackNode(data, SkuBasicPlan, "18"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), - check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("NODE|16"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("NODE|18"), ), }, data.ImportStep(), @@ -1035,6 +1041,7 @@ func TestAccLinuxFunctionApp_appStackJava(t *testing.T) { Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("JAVA|11"), ), }, data.ImportStep(), @@ -1051,14 +1058,16 @@ func TestAccLinuxFunctionApp_appStackJavaUpdate(t *testing.T) { Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("JAVA|8"), ), }, data.ImportStep(), { - Config: r.appStackJava(data, SkuBasicPlan, "11"), + Config: r.appStackJava(data, SkuBasicPlan, "17"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("JAVA|17"), ), }, data.ImportStep(), @@ -1104,6 +1113,49 @@ func TestAccLinuxFunctionApp_appStackPowerShellCore(t *testing.T) { 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"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("POWERSHELL|7"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLinuxFunctionApp_appStackPowerShellCore72(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.2"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("POWERSHELL|7.2"), + ), + }, + data.ImportStep(), + }) +} + +// (@jackofallops) - The portal does not allow the active stack to be changed currently, however, the API accepts it and the changes are reflected in the portal. +func TestAccLinuxFunctionApp_appStackUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_linux_function_app", "test") + r := LinuxFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appStackDotNet(data, SkuBasicPlan, "6.0"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + ), + }, + 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,linux"), @@ -2181,6 +2233,7 @@ resource "azurerm_linux_function_app" "test" { `, r.template(data, planSku), data.RandomInteger, pythonVersion) } +// nolint: unparam func (r LinuxFunctionAppResource) appStackJava(data acceptance.TestData, planSku string, javaVersion string) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/internal/services/appservice/linux_function_app_slot_resource.go b/internal/services/appservice/linux_function_app_slot_resource.go index 1cb16be07172..def7d62d0075 100644 --- a/internal/services/appservice/linux_function_app_slot_resource.go +++ b/internal/services/appservice/linux_function_app_slot_resource.go @@ -484,7 +484,11 @@ func (r LinuxFunctionAppSlotResource) Create() sdk.ResourceFunc { return fmt.Errorf("waiting for creation of Linux %s: %+v", id, err) } - backupConfig := helpers.ExpandBackupConfig(functionAppSlot.Backup) + backupConfig, err := helpers.ExpandBackupConfig(functionAppSlot.Backup) + if err != nil { + return fmt.Errorf("expanding backup configuration for Linux %s: %+v", id, err) + } + if backupConfig.BackupRequestProperties != nil { if _, err := client.UpdateBackupConfigurationSlot(ctx, id.ResourceGroup, id.SiteName, *backupConfig, id.SlotName); err != nil { return fmt.Errorf("adding Backup Settings for Linux %s: %+v", id, err) @@ -839,7 +843,10 @@ func (r LinuxFunctionAppSlotResource) Update() sdk.ResourceFunc { } if metadata.ResourceData.HasChange("backup") { - backupUpdate := helpers.ExpandBackupConfig(state.Backup) + backupUpdate, err := helpers.ExpandBackupConfig(state.Backup) + if err != nil { + return fmt.Errorf("expanding backup configuration for Linux %s: %+v", *id, err) + } if backupUpdate.BackupRequestProperties == nil { if _, err := client.DeleteBackupConfigurationSlot(ctx, id.ResourceGroup, id.SiteName, id.SlotName); err != nil { return fmt.Errorf("removing Backup Settings for Linux %s: %+v", id, err) diff --git a/internal/services/appservice/linux_function_app_slot_resource_test.go b/internal/services/appservice/linux_function_app_slot_resource_test.go index df7329ef8893..64c97c9858aa 100644 --- a/internal/services/appservice/linux_function_app_slot_resource_test.go +++ b/internal/services/appservice/linux_function_app_slot_resource_test.go @@ -601,13 +601,13 @@ func TestAccLinuxFunctionAppSlot_appServiceLoggingUpdate(t *testing.T) { // App Stacks -func TestAccLinuxFunctionAppSlot_appStackDotNet31(t *testing.T) { +func TestAccLinuxFunctionAppSlot_appStackCustom(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_linux_function_app_slot", "test") r := LinuxFunctionAppSlotResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.appStackDotNet(data, SkuStandardPlan, "3.1"), + Config: r.appStackCustom(data, SkuStandardPlan), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), @@ -617,16 +617,17 @@ func TestAccLinuxFunctionAppSlot_appStackDotNet31(t *testing.T) { }) } -func TestAccLinuxFunctionAppSlot_appStackCustom(t *testing.T) { +func TestAccLinuxFunctionAppSlot_appStackDotNet31(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_linux_function_app_slot", "test") r := LinuxFunctionAppSlotResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.appStackCustom(data, SkuStandardPlan), + Config: r.appStackDotNet(data, SkuStandardPlan, "3.1"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("DOTNET|3.1"), ), }, data.ImportStep(), @@ -643,6 +644,7 @@ func TestAccLinuxFunctionAppSlot_appStackDotNet6(t *testing.T) { Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("DOTNET|6.0"), ), }, data.ImportStep(), @@ -659,6 +661,7 @@ func TestAccLinuxFunctionAppSlot_appStackDotNet6Isolated(t *testing.T) { Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("DOTNET-ISOLATED|6.0"), ), }, data.ImportStep(), @@ -671,10 +674,11 @@ func TestAccLinuxFunctionAppSlot_appStackPython(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.appStackPython(data, SkuStandardPlan, "3.7"), + Config: r.appStackPython(data, SkuStandardPlan, "3.9"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("PYTHON|3.9"), ), }, data.ImportStep(), @@ -687,18 +691,20 @@ func TestAccLinuxFunctionAppSlot_appStackPythonUpdate(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.appStackPython(data, SkuStandardPlan, "3.7"), + Config: r.appStackPython(data, SkuStandardPlan, "3.9"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("PYTHON|3.9"), ), }, data.ImportStep(), { - Config: r.appStackPython(data, SkuStandardPlan, "3.9"), + Config: r.appStackPython(data, SkuStandardPlan, "3.10"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("PYTHON|3.10"), ), }, data.ImportStep(), @@ -711,10 +717,11 @@ func TestAccLinuxFunctionAppSlot_appStackNode(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.appStackNode(data, SkuStandardPlan, "14"), + Config: r.appStackNode(data, SkuStandardPlan, "16"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("NODE|16"), ), }, data.ImportStep(), @@ -727,38 +734,38 @@ func TestAccLinuxFunctionAppSlot_appStackNodeUpdate(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.appStackNode(data, SkuStandardPlan, "12"), + Config: r.appStackNode(data, SkuStandardPlan, "14"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), - check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("NODE|12"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("NODE|14"), ), }, data.ImportStep(), { - Config: r.appStackNode(data, SkuStandardPlan, "14"), + Config: r.appStackNode(data, SkuStandardPlan, "16"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), - check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("NODE|14"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("NODE|16"), ), }, data.ImportStep(), { - Config: r.appStackNode(data, SkuStandardPlan, "16"), + Config: r.appStackNode(data, SkuStandardPlan, "18"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), - check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("NODE|16"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("NODE|18"), ), }, data.ImportStep(), { - Config: r.appStackNodeUpdateTags(data, SkuStandardPlan, "16"), + Config: r.appStackNodeUpdateTags(data, SkuStandardPlan, "18"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), - check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("NODE|16"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("NODE|18"), ), }, data.ImportStep(), @@ -775,6 +782,7 @@ func TestAccLinuxFunctionAppSlot_appStackJava(t *testing.T) { Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("JAVA|11"), ), }, data.ImportStep(), @@ -791,14 +799,16 @@ func TestAccLinuxFunctionAppSlot_appStackJavaUpdate(t *testing.T) { Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("JAVA|8"), ), }, data.ImportStep(), { - Config: r.appStackJava(data, SkuStandardPlan, "11"), + Config: r.appStackJava(data, SkuStandardPlan, "17"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("JAVA|17"), ), }, data.ImportStep(), @@ -847,6 +857,24 @@ func TestAccLinuxFunctionAppSlot_appStackPowerShellCore(t *testing.T) { Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("POWERSHELL|7"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLinuxFunctionAppSlot_appStackPowerShellCore72(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_linux_function_app_slot", "test") + r := LinuxFunctionAppSlotResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appStackPowerShellCore(data, SkuStandardPlan, "7.2"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp,linux"), + check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("POWERSHELL|7.2"), ), }, data.ImportStep(), diff --git a/internal/services/appservice/linux_web_app_data_source.go b/internal/services/appservice/linux_web_app_data_source.go index 26c1ca952634..5e714212dc6b 100644 --- a/internal/services/appservice/linux_web_app_data_source.go +++ b/internal/services/appservice/linux_web_app_data_source.go @@ -282,7 +282,10 @@ func (r LinuxWebAppDataSource) Read() sdk.ResourceFunc { } var healthCheckCount *int - webApp.AppSettings, healthCheckCount = helpers.FlattenAppSettings(appSettings) + webApp.AppSettings, healthCheckCount, err = helpers.FlattenAppSettings(appSettings) + if err != nil { + return fmt.Errorf("flattening app settings for Linux %s: %+v", id, err) + } webApp.Kind = utils.NormalizeNilableString(existing.Kind) webApp.Location = location.NormalizeNilable(existing.Location) webApp.Tags = tags.ToTypedObject(existing.Tags) diff --git a/internal/services/appservice/linux_web_app_resource.go b/internal/services/appservice/linux_web_app_resource.go index 04c1c653a9e8..167a858cd5ba 100644 --- a/internal/services/appservice/linux_web_app_resource.go +++ b/internal/services/appservice/linux_web_app_resource.go @@ -351,7 +351,7 @@ func (r LinuxWebAppResource) Create() sdk.ResourceFunc { appSettings := helpers.ExpandAppSettingsForUpdate(webApp.AppSettings) if metadata.ResourceData.HasChange("site_config.0.health_check_eviction_time_in_min") { - appSettings.Properties["WEBSITE_HEALTHCHECK_MAXPINGFAILURE"] = utils.String(strconv.Itoa(webApp.SiteConfig[0].HealthCheckEvictionTime)) + appSettings.Properties["WEBSITE_HEALTHCHECK_MAXPINGFAILURES"] = utils.String(strconv.Itoa(webApp.SiteConfig[0].HealthCheckEvictionTime)) } if appSettings.Properties != nil { @@ -387,7 +387,10 @@ func (r LinuxWebAppResource) Create() sdk.ResourceFunc { } } - backupConfig := helpers.ExpandBackupConfig(webApp.Backup) + backupConfig, err := helpers.ExpandBackupConfig(webApp.Backup) + if err != nil { + return fmt.Errorf("expanding backup configuration for Linux %s: %+v", id, err) + } if backupConfig.BackupRequestProperties != nil { if _, err := client.UpdateBackupConfiguration(ctx, id.ResourceGroup, id.SiteName, *backupConfig); err != nil { return fmt.Errorf("adding Backup Settings for Linux %s: %+v", id, err) @@ -523,7 +526,10 @@ func (r LinuxWebAppResource) Read() sdk.ResourceFunc { } var healthCheckCount *int - state.AppSettings, healthCheckCount = helpers.FlattenAppSettings(appSettings) + state.AppSettings, healthCheckCount, err = helpers.FlattenAppSettings(appSettings) + if err != nil { + return fmt.Errorf("flattening app settings for Linux %s: %+v", id, err) + } if v := props.OutboundIPAddresses; v != nil { state.OutboundIPAddresses = *v @@ -706,11 +712,10 @@ func (r LinuxWebAppResource) Update() sdk.ResourceFunc { } // (@jackofallops) - App Settings can clobber logs configuration so must be updated before we send any Log updates - if metadata.ResourceData.HasChange("app_settings") { + if metadata.ResourceData.HasChange("app_settings") || metadata.ResourceData.HasChange("site_config.0.health_check_eviction_time_in_min") { appSettingsUpdate := helpers.ExpandAppSettingsForUpdate(state.AppSettings) - if metadata.ResourceData.HasChange("site_config.0.health_check_eviction_time_in_min") { - appSettingsUpdate.Properties["WEBSITE_HEALTHCHECK_MAXPINGFAILURE"] = utils.String(strconv.Itoa(state.SiteConfig[0].HealthCheckEvictionTime)) - } + appSettingsUpdate.Properties["WEBSITE_HEALTHCHECK_MAXPINGFAILURES"] = utils.String(strconv.Itoa(state.SiteConfig[0].HealthCheckEvictionTime)) + if _, err := client.UpdateApplicationSettings(ctx, id.ResourceGroup, id.SiteName, *appSettingsUpdate); err != nil { return fmt.Errorf("updating App Settings for Linux %s: %+v", id, err) } @@ -758,7 +763,10 @@ func (r LinuxWebAppResource) Update() sdk.ResourceFunc { } if metadata.ResourceData.HasChange("backup") { - backupUpdate := helpers.ExpandBackupConfig(state.Backup) + backupUpdate, err := helpers.ExpandBackupConfig(state.Backup) + if err != nil { + return fmt.Errorf("expanding backup configuration for Linux %s: %+v", *id, err) + } if backupUpdate.BackupRequestProperties == nil { if _, err := client.DeleteBackupConfiguration(ctx, id.ResourceGroup, id.SiteName); err != nil { return fmt.Errorf("removing Backup Settings for Linux %s: %+v", id, err) diff --git a/internal/services/appservice/linux_web_app_resource_test.go b/internal/services/appservice/linux_web_app_resource_test.go index cb8714eba180..06765576c211 100644 --- a/internal/services/appservice/linux_web_app_resource_test.go +++ b/internal/services/appservice/linux_web_app_resource_test.go @@ -613,6 +613,36 @@ func TestAccLinuxWebApp_withDotNet70(t *testing.T) { }) } +func TestAccLinuxWebApp_withGo18(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_linux_web_app", "test") + r := LinuxWebAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.applicationStackGo(data, "1.18"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLinuxWebApp_withGo19(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_linux_web_app", "test") + r := LinuxWebAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.applicationStackGo(data, "1.19"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccLinuxWebApp_withPhp74(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_linux_web_app", "test") r := LinuxWebAppResource{} @@ -763,13 +793,28 @@ func TestAccLinuxWebApp_withNode14LTS(t *testing.T) { }) } +func TestAccLinuxWebApp_withNode18LTS(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_linux_web_app", "test") + r := LinuxWebAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.node(data, "18-lts"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccLinuxWebApp_withJre8Java(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_linux_web_app", "test") r := LinuxWebAppResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "jre8", "JAVA", "8"), + Config: r.java(data, "8", "JAVA", "8"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -784,7 +829,7 @@ func TestAccLinuxWebApp_withJre11Java(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "java11", "JAVA", "11"), + Config: r.java(data, "11", "JAVA", "11"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("JAVA|11-java11"), @@ -800,7 +845,7 @@ func TestAccLinuxWebApp_withJava1109(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "11.0.9", "JAVA", ""), + Config: r.java(data, "11", "JAVA", "11.0.9"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("JAVA|11.0.9"), @@ -816,7 +861,7 @@ func TestAccLinuxWebApp_withJava8u242(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "8u242", "JAVA", ""), + Config: r.java(data, "8", "JAVA", "8u242"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("JAVA|8u242"), @@ -832,7 +877,7 @@ func TestAccLinuxWebApp_withJava11Tomcat9(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "java11", "TOMCAT", "9.0"), + Config: r.java(data, "11", "TOMCAT", "9.0"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("TOMCAT|9.0-java11"), @@ -848,7 +893,7 @@ func TestAccLinuxWebApp_withJava11Tomcat9041(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "java11", "TOMCAT", "9.0.41"), + Config: r.java(data, "11", "TOMCAT", "9.0.41"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("TOMCAT|9.0.41-java11"), @@ -864,7 +909,7 @@ func TestAccLinuxWebApp_withJava11Tomcat85(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "java11", "TOMCAT", "8.5"), + Config: r.java(data, "11", "TOMCAT", "8.5"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("TOMCAT|8.5-java11"), @@ -880,7 +925,7 @@ func TestAccLinuxWebApp_withJava11Tomcat8561(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "java11", "TOMCAT", "8.5.61"), + Config: r.java(data, "11", "TOMCAT", "8.5.61"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("TOMCAT|8.5.61-java11"), @@ -896,7 +941,7 @@ func TestAccLinuxWebApp_withJava8JBOSSEAP73(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.javaPremiumV3Plan(data, "java8", "JBOSSEAP", "7.3"), + Config: r.javaPremiumV3Plan(data, "8", "JBOSSEAP", "7.3"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("JBOSSEAP|7.3-java8"), @@ -939,7 +984,7 @@ func TestAccLinuxWebApp_updateAppStack(t *testing.T) { }, data.ImportStep(), { - Config: r.java(data, "java11", "TOMCAT", "9.0"), + Config: r.java(data, "11", "TOMCAT", "9.0"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("TOMCAT|9.0-java11"), @@ -2327,6 +2372,29 @@ resource "azurerm_linux_web_app" "test" { `, r.baseTemplate(data), data.RandomInteger, dotNetVersion) } +func (r LinuxWebAppResource) applicationStackGo(data acceptance.TestData, goVersion string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_linux_web_app" "test" { + name = "acctestWA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + site_config { + application_stack { + go_version = "%s" + } + } +} +`, r.baseTemplate(data), data.RandomInteger, goVersion) +} + func (r LinuxWebAppResource) php(data acceptance.TestData, phpVersion string) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/internal/services/appservice/linux_web_app_slot_resource.go b/internal/services/appservice/linux_web_app_slot_resource.go index 897fb30613a1..3a6f8c6dd629 100644 --- a/internal/services/appservice/linux_web_app_slot_resource.go +++ b/internal/services/appservice/linux_web_app_slot_resource.go @@ -319,7 +319,7 @@ func (r LinuxWebAppSlotResource) Create() sdk.ResourceFunc { appSettings := helpers.ExpandAppSettingsForUpdate(webAppSlot.AppSettings) if metadata.ResourceData.HasChange("site_config.0.health_check_eviction_time_in_min") { - appSettings.Properties["WEBSITE_HEALTHCHECK_MAXPINGFAILURE"] = utils.String(strconv.Itoa(webAppSlot.SiteConfig[0].HealthCheckEvictionTime)) + appSettings.Properties["WEBSITE_HEALTHCHECK_MAXPINGFAILURES"] = utils.String(strconv.Itoa(webAppSlot.SiteConfig[0].HealthCheckEvictionTime)) } if appSettings.Properties != nil { @@ -344,7 +344,10 @@ func (r LinuxWebAppSlotResource) Create() sdk.ResourceFunc { } } - backupConfig := helpers.ExpandBackupConfig(webAppSlot.Backup) + backupConfig, err := helpers.ExpandBackupConfig(webAppSlot.Backup) + if err != nil { + return fmt.Errorf("expanding backup configuration for Linux %s: %+v", id, err) + } if backupConfig.BackupRequestProperties != nil { if _, err := client.UpdateBackupConfigurationSlot(ctx, id.ResourceGroup, id.SiteName, *backupConfig, id.SlotName); err != nil { return fmt.Errorf("adding Backup Settings for Linux %s: %+v", id, err) @@ -472,7 +475,10 @@ func (r LinuxWebAppSlotResource) Read() sdk.ResourceFunc { } var healthCheckCount *int - state.AppSettings, healthCheckCount = helpers.FlattenAppSettings(appSettings) + state.AppSettings, healthCheckCount, err = helpers.FlattenAppSettings(appSettings) + if err != nil { + return fmt.Errorf("flattening app settings for Linux %s: %+v", id, err) + } if v := props.OutboundIPAddresses; v != nil { state.OutboundIPAddresses = *v @@ -630,11 +636,10 @@ func (r LinuxWebAppSlotResource) Update() sdk.ResourceFunc { } // (@jackofallops) - App Settings can clobber logs configuration so must be updated before we send any Log updates - if metadata.ResourceData.HasChange("app_settings") { + if metadata.ResourceData.HasChange("app_settings") || metadata.ResourceData.HasChange("site_config.0.health_check_eviction_time_in_min") { appSettingsUpdate := helpers.ExpandAppSettingsForUpdate(state.AppSettings) - if metadata.ResourceData.HasChange("site_config.0.health_check_eviction_time_in_min") { - appSettingsUpdate.Properties["WEBSITE_HEALTHCHECK_MAXPINGFAILURE"] = utils.String(strconv.Itoa(state.SiteConfig[0].HealthCheckEvictionTime)) - } + appSettingsUpdate.Properties["WEBSITE_HEALTHCHECK_MAXPINGFAILURES"] = utils.String(strconv.Itoa(state.SiteConfig[0].HealthCheckEvictionTime)) + if _, err := client.UpdateApplicationSettingsSlot(ctx, id.ResourceGroup, id.SiteName, *appSettingsUpdate, id.SlotName); err != nil { return fmt.Errorf("updating App Settings for Linux %s: %+v", id, err) } @@ -658,7 +663,10 @@ func (r LinuxWebAppSlotResource) Update() sdk.ResourceFunc { } if metadata.ResourceData.HasChange("backup") { - backupUpdate := helpers.ExpandBackupConfig(state.Backup) + backupUpdate, err := helpers.ExpandBackupConfig(state.Backup) + if err != nil { + return fmt.Errorf("expanding backup configuration for Linux %s: %+v", *id, err) + } if backupUpdate.BackupRequestProperties == nil { if _, err := client.DeleteBackupConfigurationSlot(ctx, id.ResourceGroup, id.SiteName, id.SlotName); err != nil { return fmt.Errorf("removing Backup Settings for Linux %s: %+v", id, err) diff --git a/internal/services/appservice/linux_web_app_slot_resource_test.go b/internal/services/appservice/linux_web_app_slot_resource_test.go index 541e23bd9f75..25db9950b8bd 100644 --- a/internal/services/appservice/linux_web_app_slot_resource_test.go +++ b/internal/services/appservice/linux_web_app_slot_resource_test.go @@ -797,7 +797,7 @@ func TestAccLinuxWebAppSlot_withJre8Java(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "jre8", "JAVA", "8"), + Config: r.java(data, "8", "JAVA", "8"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -812,7 +812,7 @@ func TestAccLinuxWebAppSlot_withJre11Java(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "java11", "JAVA", "11"), + Config: r.java(data, "11", "JAVA", "11"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("JAVA|11-java11"), @@ -828,7 +828,7 @@ func TestAccLinuxWebAppSlot_withJava1109(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "11.0.9", "JAVA", ""), + Config: r.java(data, "11", "JAVA", "11.0.9"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("JAVA|11.0.9"), @@ -844,7 +844,7 @@ func TestAccLinuxWebAppSlot_withJava8u242(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "8u242", "JAVA", ""), + Config: r.java(data, "8", "JAVA", "8u242"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("JAVA|8u242"), @@ -860,7 +860,7 @@ func TestAccLinuxWebAppSlot_withJava11Tomcat9(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "java11", "TOMCAT", "9.0"), + Config: r.java(data, "11", "TOMCAT", "9.0"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("TOMCAT|9.0-java11"), @@ -876,7 +876,7 @@ func TestAccLinuxWebAppSlot_withJava11Tomcat8561(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "java11", "TOMCAT", "8.5.61"), + Config: r.java(data, "11", "TOMCAT", "8.5.61"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("TOMCAT|8.5.61-java11"), @@ -892,7 +892,7 @@ func TestAccLinuxWebAppSlot_withJava8JBOSSEAP73(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.javaPremiumV3Plan(data, "java8", "JBOSSEAP", "7.3"), + Config: r.javaPremiumV3Plan(data, "8", "JBOSSEAP", "7.3"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("site_config.0.linux_fx_version").HasValue("JBOSSEAP|7.3-java8"), diff --git a/internal/services/appservice/validate/auto_heal_interval.go b/internal/services/appservice/validate/auto_heal_interval.go new file mode 100644 index 000000000000..609e66421441 --- /dev/null +++ b/internal/services/appservice/validate/auto_heal_interval.go @@ -0,0 +1,19 @@ +package validate + +import ( + "fmt" + "regexp" +) + +func AutoHealInterval(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return warnings, errors + } + if matched := regexp.MustCompile(`^([0-9][0-9]):([0-5][0-9]):([0-5][0-9])$`).Match([]byte(v)); !matched { + errors = append(errors, fmt.Errorf("%q must be in the form HH:MM:SS between 00:00:00 and 99:59:59", k)) + } + + return warnings, errors +} diff --git a/internal/services/appservice/validate/auto_heal_interval_test.go b/internal/services/appservice/validate/auto_heal_interval_test.go new file mode 100644 index 000000000000..91ef5f038188 --- /dev/null +++ b/internal/services/appservice/validate/auto_heal_interval_test.go @@ -0,0 +1,41 @@ +package validate + +import "testing" + +func TestAutoHealInterval(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + { + Input: "100:00:00", + }, + { + Input: "10:70:00", + }, + { + Input: "10:00:61", + }, + { + Input: "00:00:00", + Valid: true, + }, + { + Input: "07:45:11", + Valid: true, + }, + { + Input: "99:00:00", + Valid: true, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := AutoHealInterval(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/internal/services/appservice/validate/status_code_range.go b/internal/services/appservice/validate/status_code_range.go new file mode 100644 index 000000000000..5c3587adc15e --- /dev/null +++ b/internal/services/appservice/validate/status_code_range.go @@ -0,0 +1,42 @@ +package validate + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +func StatusCodeRange(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return warnings, errors + } + parts := strings.Split(v, "-") + if len(parts) < 1 || len(parts) > 2 { + errors = append(errors, fmt.Errorf("%q must be either a single HTTP status code or a range in the form 100-599", k)) + } + + for _, part := range parts { + if matched := regexp.MustCompile(`^([1-5][0-9][0-9])$`).Match([]byte(part)); !matched { + errors = append(errors, fmt.Errorf("%q must be either a single HTTP status code or a range in the form 100-599", k)) + } + } + + if len(parts) == 2 { + lowCode, err := strconv.Atoi(parts[0]) + if err != nil { + errors = append(errors, fmt.Errorf("could not convert status code low value (%+v) to int", parts[1])) + } + highCode, err := strconv.Atoi(parts[1]) + if err != nil { + errors = append(errors, fmt.Errorf("could not convert status code high value (%+v) to int", parts[1])) + } + if lowCode > highCode { + errors = append(errors, fmt.Errorf("%q range values must be in the form low to high, e.g. 200-30. Got %+v", k, v)) + } + } + + return +} diff --git a/internal/services/appservice/validate/status_code_range_test.go b/internal/services/appservice/validate/status_code_range_test.go new file mode 100644 index 000000000000..87ae85b565f0 --- /dev/null +++ b/internal/services/appservice/validate/status_code_range_test.go @@ -0,0 +1,45 @@ +package validate + +import "testing" + +func TestStatusCodeRange(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + { + Input: "10", + }, + { + Input: "999", + }, + { + Input: "100-600", + }, + { + Input: "300-200", + }, + { + Input: "200", + Valid: true, + }, + { + Input: "300-302", + Valid: true, + }, + { + Input: "100-599", + Valid: true, + }, + } + + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := StatusCodeRange(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/internal/services/appservice/windows_function_app_resource.go b/internal/services/appservice/windows_function_app_resource.go index 780b39400977..0e34b0a44650 100644 --- a/internal/services/appservice/windows_function_app_resource.go +++ b/internal/services/appservice/windows_function_app_resource.go @@ -9,6 +9,7 @@ import ( "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2021-02-01/web" // nolint: staticcheck "github.com/google/uuid" + "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/go-azure-helpers/resourcemanager/commonids" "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" "github.com/hashicorp/go-azure-helpers/resourcemanager/location" @@ -435,7 +436,6 @@ func (r WindowsFunctionAppResource) Create() sdk.ResourceFunc { } } - siteConfig.WindowsFxVersion = helpers.EncodeFunctionAppWindowsFxVersion(functionApp.SiteConfig[0].ApplicationStack) siteConfig.AppSettings = helpers.MergeUserAppSettings(siteConfig.AppSettings, functionApp.AppSettings) expandedIdentity, err := expandIdentity(metadata.ResourceData.Get("identity").([]interface{})) @@ -499,7 +499,10 @@ func (r WindowsFunctionAppResource) Create() sdk.ResourceFunc { } } - backupConfig := helpers.ExpandBackupConfig(functionApp.Backup) + backupConfig, err := helpers.ExpandBackupConfig(functionApp.Backup) + if err != nil { + return fmt.Errorf("expanding backup configuration for Windows %s: %+v", id, err) + } 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) @@ -650,6 +653,7 @@ func (r WindowsFunctionAppResource) Read() sdk.ResourceFunc { if err != nil { return fmt.Errorf("reading Site Config for Windows %s: %+v", id, err) } + state.SiteConfig = []helpers.SiteConfigWindowsFunctionApp{*siteConfig} state.unpackWindowsFunctionAppSettings(appSettingsResp, metadata) @@ -843,10 +847,6 @@ func (r WindowsFunctionAppResource) Update() sdk.ResourceFunc { 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) @@ -903,7 +903,11 @@ func (r WindowsFunctionAppResource) Update() sdk.ResourceFunc { } if metadata.ResourceData.HasChange("backup") { - backupUpdate := helpers.ExpandBackupConfig(state.Backup) + backupUpdate, err := helpers.ExpandBackupConfig(state.Backup) + if err != nil { + return fmt.Errorf("expanding backup configuration for Windows %s: %+v", *id, err) + } + 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) @@ -1043,7 +1047,11 @@ func (m *WindowsFunctionAppModel) unpackWindowsFunctionAppSettings(input web.Str 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_NODE_DEFAULT_VERSION": + if len(m.SiteConfig[0].ApplicationStack) == 0 { + m.SiteConfig[0].ApplicationStack = []helpers.ApplicationStackWindowsFunctionApp{{}} + } + m.SiteConfig[0].ApplicationStack[0].NodeVersion = pointer.From(v) case "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": if _, ok := metadata.ResourceData.GetOk("app_settings.WEBSITE_CONTENTAZUREFILECONNECTIONSTRING"); ok { appSettings[k] = utils.NormalizeNilableString(v) @@ -1056,13 +1064,16 @@ func (m *WindowsFunctionAppModel) unpackWindowsFunctionAppSettings(input web.Str case "WEBSITE_HTTPLOGGING_RETENTION_DAYS": case "FUNCTIONS_WORKER_RUNTIME": - if len(m.SiteConfig) > 0 && len(m.SiteConfig[0].ApplicationStack) > 0 { - m.SiteConfig[0].ApplicationStack[0].CustomHandler = strings.EqualFold(*v, "custom") - } - if _, ok := metadata.ResourceData.GetOk("app_settings.FUNCTIONS_WORKER_RUNTIME"); ok { appSettings[k] = utils.NormalizeNilableString(v) } + switch *v { + case "dotnet-isolated": + m.SiteConfig[0].ApplicationStack[0].DotNetIsolated = true + case "custom": + m.SiteConfig[0].ApplicationStack[0].CustomHandler = true + + } case "DOCKER_REGISTRY_SERVER_URL": dockerSettings.RegistryURL = utils.NormalizeNilableString(v) diff --git a/internal/services/appservice/windows_function_app_resource_test.go b/internal/services/appservice/windows_function_app_resource_test.go index 843514207456..d01a94e3ebd5 100644 --- a/internal/services/appservice/windows_function_app_resource_test.go +++ b/internal/services/appservice/windows_function_app_resource_test.go @@ -873,13 +873,13 @@ func TestAccWindowsFunctionApp_identityKeyVaultIdentity(t *testing.T) { // App Stacks -func TestAccWindowsFunctionApp_appStackDotNet31(t *testing.T) { +func TestAccWindowsFunctionApp_appStackDotNet30(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"), + Config: r.appStackDotNet(data, SkuBasicPlan, "v3.0"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp"), @@ -895,7 +895,7 @@ func TestAccWindowsFunctionApp_appStackDotNet6(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.appStackDotNet(data, SkuBasicPlan, "6"), + Config: r.appStackDotNet(data, SkuBasicPlan, "v6.0"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp"), @@ -911,11 +911,10 @@ func TestAccWindowsFunctionApp_appStackDotNet6Isolated(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.appStackDotNetIsolated(data, SkuBasicPlan, "6"), + Config: r.appStackDotNetIsolated(data, SkuBasicPlan, "v6.0"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp"), - check.That(data.ResourceName).Key("site_config.0.windows_fx_version").HasValue("DotNet-Isolated|6"), ), }, data.ImportStep(), @@ -938,6 +937,22 @@ func TestAccWindowsFunctionApp_appStackNode(t *testing.T) { }) } +func TestAccWindowsFunctionApp_appStackNode18(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") + r := WindowsFunctionAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appStackNode(data, SkuConsumptionPlan, "~18"), + 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{} @@ -980,7 +995,6 @@ func TestAccWindowsFunctionApp_appStackUpdateTags(t *testing.T) { Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp"), - check.That(data.ResourceName).Key("site_config.0.windows_fx_version").HasValue("Node|~14"), ), }, data.ImportStep(), @@ -989,14 +1003,29 @@ func TestAccWindowsFunctionApp_appStackUpdateTags(t *testing.T) { Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp"), - check.That(data.ResourceName).Key("site_config.0.windows_fx_version").HasValue("Node|~14"), ), }, data.ImportStep(), }) } -func TestAccWindowsFunctionApp_appStackJava(t *testing.T) { +func TestAccWindowsFunctionApp_appStackJava8(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, "1.8"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsFunctionApp_appStackJava11(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") r := WindowsFunctionAppResource{} @@ -1018,7 +1047,7 @@ func TestAccWindowsFunctionApp_appStackJavaUpdate(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.appStackJava(data, SkuBasicPlan, "8"), + Config: r.appStackJava(data, SkuBasicPlan, "11"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp"), @@ -1026,7 +1055,7 @@ func TestAccWindowsFunctionApp_appStackJavaUpdate(t *testing.T) { }, data.ImportStep(), { - Config: r.appStackJava(data, SkuBasicPlan, "11"), + Config: r.appStackJava(data, SkuBasicPlan, "17"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp"), @@ -1036,7 +1065,7 @@ func TestAccWindowsFunctionApp_appStackJavaUpdate(t *testing.T) { }) } -func TestAccWindowsFunctionApp_appStackPowerShellCore(t *testing.T) { +func TestAccWindowsFunctionApp_appStackPowerShellCore7(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_windows_function_app", "test") r := WindowsFunctionAppResource{} @@ -1052,6 +1081,22 @@ func TestAccWindowsFunctionApp_appStackPowerShellCore(t *testing.T) { }) } +func TestAccWindowsFunctionApp_appStackPowerShellCore72(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.2"), + 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) { @@ -2571,6 +2616,7 @@ resource "azurerm_windows_function_app" "test" { `, r.template(data, planSku), data.RandomInteger, nodeVersion) } +// nolint: unparam func (r WindowsFunctionAppResource) appStackJava(data acceptance.TestData, planSku string, javaVersion string) string { return fmt.Sprintf(` provider "azurerm" { @@ -2814,7 +2860,7 @@ resource "azurerm_windows_function_app" "test" { site_config { application_stack { - dotnet_version = "6" + dotnet_version = "v6.0" } } } diff --git a/internal/services/appservice/windows_function_app_slot_resource.go b/internal/services/appservice/windows_function_app_slot_resource.go index 1da0dc327001..7576bf042e9e 100644 --- a/internal/services/appservice/windows_function_app_slot_resource.go +++ b/internal/services/appservice/windows_function_app_slot_resource.go @@ -9,6 +9,7 @@ import ( "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2021-02-01/web" // nolint: staticcheck "github.com/google/uuid" + "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/go-azure-helpers/resourcemanager/commonids" "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" @@ -440,7 +441,6 @@ func (r WindowsFunctionAppSlotResource) Create() sdk.ResourceFunc { } } - siteConfig.WindowsFxVersion = helpers.EncodeFunctionAppWindowsFxVersion(functionAppSlot.SiteConfig[0].ApplicationStack) siteConfig.AppSettings = helpers.MergeUserAppSettings(siteConfig.AppSettings, functionAppSlot.AppSettings) expandedIdentity, err := expandIdentity(metadata.ResourceData.Get("identity").([]interface{})) @@ -493,7 +493,11 @@ func (r WindowsFunctionAppSlotResource) Create() sdk.ResourceFunc { return fmt.Errorf("waiting for creation of Windows %s: %+v", id, err) } - backupConfig := helpers.ExpandBackupConfig(functionAppSlot.Backup) + backupConfig, err := helpers.ExpandBackupConfig(functionAppSlot.Backup) + if err != nil { + return fmt.Errorf("expanding backup configuration for Windows %s: %+v", id, err) + } + if backupConfig.BackupRequestProperties != nil { if _, err := client.UpdateBackupConfigurationSlot(ctx, id.ResourceGroup, id.SiteName, *backupConfig, id.SlotName); err != nil { return fmt.Errorf("adding Backup Settings for Windows %s: %+v", id, err) @@ -814,10 +818,6 @@ func (r WindowsFunctionAppSlotResource) Update() sdk.ResourceFunc { 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.CreateOrUpdateSlot(ctx, id.ResourceGroup, id.SiteName, existing, id.SlotName) @@ -850,7 +850,11 @@ func (r WindowsFunctionAppSlotResource) Update() sdk.ResourceFunc { } if metadata.ResourceData.HasChange("backup") { - backupUpdate := helpers.ExpandBackupConfig(state.Backup) + backupUpdate, err := helpers.ExpandBackupConfig(state.Backup) + if err != nil { + return fmt.Errorf("expanding backup configuration for Windows %s: %+v", *id, err) + } + if backupUpdate.BackupRequestProperties == nil { if _, err := client.DeleteBackupConfigurationSlot(ctx, id.ResourceGroup, id.SiteName, id.SlotName); err != nil { return fmt.Errorf("removing Backup Settings for Windows %s: %+v", id, err) @@ -888,7 +892,12 @@ func (m *WindowsFunctionAppSlotModel) unpackWindowsFunctionAppSettings(input web 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_NODE_DEFAULT_VERSION": + if len(m.SiteConfig[0].ApplicationStack) == 0 { + m.SiteConfig[0].ApplicationStack = []helpers.ApplicationStackWindowsFunctionApp{{}} + } + m.SiteConfig[0].ApplicationStack[0].NodeVersion = pointer.From(v) + case "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": if _, ok := metadata.ResourceData.GetOk("app_settings.WEBSITE_CONTENTAZUREFILECONNECTIONSTRING"); ok { appSettings[k] = utils.NormalizeNilableString(v) @@ -901,14 +910,15 @@ func (m *WindowsFunctionAppSlotModel) unpackWindowsFunctionAppSettings(input web 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") - } - if _, ok := metadata.ResourceData.GetOk("app_settings.FUNCTIONS_WORKER_RUNTIME"); ok { appSettings[k] = utils.NormalizeNilableString(v) } - + switch *v { + case "dotnet-isolated": + m.SiteConfig[0].ApplicationStack[0].DotNetIsolated = true + case "custom": + m.SiteConfig[0].ApplicationStack[0].CustomHandler = true + } case "DOCKER_REGISTRY_SERVER_URL": dockerSettings.RegistryURL = utils.NormalizeNilableString(v) diff --git a/internal/services/appservice/windows_function_app_slot_resource_test.go b/internal/services/appservice/windows_function_app_slot_resource_test.go index 690eca19698b..894c49c7e158 100644 --- a/internal/services/appservice/windows_function_app_slot_resource_test.go +++ b/internal/services/appservice/windows_function_app_slot_resource_test.go @@ -608,7 +608,7 @@ func TestAccWindowsFunctionAppSlot_appStackDotNet31(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.appStackDotNet(data, SkuStandardPlan, "3.1"), + Config: r.appStackDotNet(data, SkuStandardPlan, "v3.0"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp"), @@ -624,7 +624,7 @@ func TestAccWindowsFunctionAppSlot_appStackDotNet6(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.appStackDotNet(data, SkuStandardPlan, "6"), + Config: r.appStackDotNet(data, SkuStandardPlan, "v6.0"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp"), @@ -640,7 +640,7 @@ func TestAccWindowsFunctionAppSlot_appStackDotNet6Isolated(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.appStackDotNetIsolated(data, SkuStandardPlan, "6"), + Config: r.appStackDotNetIsolated(data, SkuStandardPlan, "v6.0"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp"), @@ -656,7 +656,7 @@ func TestAccWindowsFunctionAppSlot_appStackNode(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.appStackNode(data, SkuStandardPlan, "~14"), + Config: r.appStackNode(data, SkuStandardPlan, "~18"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp"), @@ -672,7 +672,7 @@ func TestAccWindowsFunctionAppSlot_appStackNodeUpdate(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.appStackNode(data, SkuStandardPlan, "~12"), + Config: r.appStackNode(data, SkuStandardPlan, "~16"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp"), @@ -680,7 +680,7 @@ func TestAccWindowsFunctionAppSlot_appStackNodeUpdate(t *testing.T) { }, data.ImportStep(), { - Config: r.appStackNode(data, SkuStandardPlan, "~14"), + Config: r.appStackNode(data, SkuStandardPlan, "~18"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp"), @@ -712,7 +712,7 @@ func TestAccWindowsFunctionAppSlot_appStackJavaUpdate(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.appStackJava(data, SkuStandardPlan, "8"), + Config: r.appStackJava(data, SkuStandardPlan, "1.8"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp"), @@ -720,7 +720,7 @@ func TestAccWindowsFunctionAppSlot_appStackJavaUpdate(t *testing.T) { }, data.ImportStep(), { - Config: r.appStackJava(data, SkuStandardPlan, "11"), + Config: r.appStackJava(data, SkuStandardPlan, "17"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("kind").HasValue("functionapp"), @@ -730,7 +730,7 @@ func TestAccWindowsFunctionAppSlot_appStackJavaUpdate(t *testing.T) { }) } -func TestAccWindowsFunctionAppSlot_appStackPowerShellCore(t *testing.T) { +func TestAccWindowsFunctionAppSlot_appStackPowerShellCore7(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_windows_function_app_slot", "test") r := WindowsFunctionAppSlotResource{} @@ -746,6 +746,22 @@ func TestAccWindowsFunctionAppSlot_appStackPowerShellCore(t *testing.T) { }) } +func TestAccWindowsFunctionAppSlot_appStackPowerShellCore72(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_function_app_slot", "test") + r := WindowsFunctionAppSlotResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.appStackPowerShellCore(data, SkuStandardPlan, "7.2"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("kind").HasValue("functionapp"), + ), + }, + data.ImportStep(), + }) +} + // Others func TestAccWindowsFunctionAppSlot_identity(t *testing.T) { @@ -2052,6 +2068,7 @@ resource "azurerm_windows_function_app_slot" "test" { `, r.template(data, planSku), data.RandomInteger, nodeVersion) } +// nolint: unparam func (r WindowsFunctionAppSlotResource) appStackJava(data acceptance.TestData, planSku string, javaVersion string) string { return fmt.Sprintf(` provider "azurerm" { @@ -2270,7 +2287,7 @@ resource "azurerm_windows_function_app_slot" "test" { site_config { application_stack { - dotnet_version = "6" + dotnet_version = "v6.0" } } } diff --git a/internal/services/appservice/windows_web_app_data_source.go b/internal/services/appservice/windows_web_app_data_source.go index 386a031a0533..53ebad72ca6a 100644 --- a/internal/services/appservice/windows_web_app_data_source.go +++ b/internal/services/appservice/windows_web_app_data_source.go @@ -272,7 +272,11 @@ func (d WindowsWebAppDataSource) Read() sdk.ResourceFunc { } var healthCheckCount *int - webApp.AppSettings, healthCheckCount = helpers.FlattenAppSettings(appSettings) + webApp.AppSettings, healthCheckCount, err = helpers.FlattenAppSettings(appSettings) + if err != nil { + return fmt.Errorf("flattening app settings for Windows %s: %+v", id, err) + } + webApp.Kind = utils.NormalizeNilableString(existing.Kind) webApp.Location = location.NormalizeNilable(existing.Location) webApp.Tags = tags.ToTypedObject(existing.Tags) @@ -312,8 +316,10 @@ func (d WindowsWebAppDataSource) Read() sdk.ResourceFunc { if ok { currentStack = *currentStackPtr } - webApp.SiteConfig = helpers.FlattenSiteConfigWindows(webAppSiteConfig.SiteConfig, currentStack, healthCheckCount) - + webApp.SiteConfig, err = helpers.FlattenSiteConfigWindows(webAppSiteConfig.SiteConfig, currentStack, healthCheckCount) + if err != nil { + return fmt.Errorf("reading API Management ID for %s: %+v", id, err) + } webApp.StickySettings = helpers.FlattenStickySettings(stickySettings.SlotConfigNames) webApp.StorageAccounts = helpers.FlattenStorageAccounts(storageAccounts) diff --git a/internal/services/appservice/windows_web_app_resource.go b/internal/services/appservice/windows_web_app_resource.go index c128e3e5e50e..cf2de3f4ebab 100644 --- a/internal/services/appservice/windows_web_app_resource.go +++ b/internal/services/appservice/windows_web_app_resource.go @@ -3,6 +3,7 @@ package appservice import ( "context" "fmt" + "strconv" "strings" "time" @@ -302,6 +303,13 @@ func (r WindowsWebAppResource) Create() sdk.ResourceFunc { return err } + if *currentStack == helpers.CurrentStackNode { + if webApp.AppSettings == nil { + webApp.AppSettings = make(map[string]string, 0) + } + webApp.AppSettings["WEBSITE_NODE_DEFAULT_VERSION"] = webApp.SiteConfig[0].ApplicationStack[0].NodeVersion + } + siteConfig.AppSettings = helpers.ExpandAppSettingsForCreate(webApp.AppSettings) expandedIdentity, err := expandIdentity(metadata.ResourceData.Get("identity").([]interface{})) @@ -356,6 +364,10 @@ func (r WindowsWebAppResource) Create() sdk.ResourceFunc { } appSettings := helpers.ExpandAppSettingsForUpdate(webApp.AppSettings) + if metadata.ResourceData.HasChange("site_config.0.health_check_eviction_time_in_min") { + appSettings.Properties["WEBSITE_HEALTHCHECK_MAXPINGFAILURES"] = utils.String(strconv.Itoa(webApp.SiteConfig[0].HealthCheckEvictionTime)) + } + if appSettings != nil { if _, err := client.UpdateApplicationSettings(ctx, id.ResourceGroup, id.SiteName, *appSettings); err != nil { return fmt.Errorf("setting App Settings for Windows %s: %+v", id, err) @@ -389,7 +401,11 @@ func (r WindowsWebAppResource) Create() sdk.ResourceFunc { } } - backupConfig := helpers.ExpandBackupConfig(webApp.Backup) + backupConfig, err := helpers.ExpandBackupConfig(webApp.Backup) + if err != nil { + return fmt.Errorf("expanding backup configuration for Windows %s: %+v", id, err) + } + 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) @@ -539,7 +555,10 @@ func (r WindowsWebAppResource) Read() sdk.ResourceFunc { } var healthCheckCount *int - state.AppSettings, healthCheckCount = helpers.FlattenAppSettings(appSettings) + state.AppSettings, healthCheckCount, err = helpers.FlattenAppSettings(appSettings) + if err != nil { + return fmt.Errorf("flattening app settings for Windows %s: %+v", id, err) + } if v := props.OutboundIPAddresses; v != nil { state.OutboundIPAddresses = *v @@ -557,7 +576,17 @@ func (r WindowsWebAppResource) Read() sdk.ResourceFunc { currentStack = *currentStackPtr } - state.SiteConfig = helpers.FlattenSiteConfigWindows(webAppSiteConfig.SiteConfig, currentStack, healthCheckCount) + state.SiteConfig, err = helpers.FlattenSiteConfigWindows(webAppSiteConfig.SiteConfig, currentStack, healthCheckCount) + if err != nil { + return fmt.Errorf("reading %s: %+v", *id, err) + } + if nodeVer, ok := state.AppSettings["WEBSITE_NODE_DEFAULT_VERSION"]; ok { + if state.SiteConfig[0].ApplicationStack == nil { + state.SiteConfig[0].ApplicationStack = make([]helpers.ApplicationStackWindows, 0) + } + state.SiteConfig[0].ApplicationStack[0].NodeVersion = nodeVer + delete(state.AppSettings, "WEBSITE_NODE_DEFAULT_VERSION") + } // Zip Deploys are not retrievable, so attempt to get from config. This doesn't matter for imports as an unexpected value here could break the deployment. if deployFile, ok := metadata.ResourceData.Get("zip_deploy_file").(string); ok { @@ -729,8 +758,9 @@ func (r WindowsWebAppResource) Update() sdk.ResourceFunc { } // (@jackofallops) - App Settings can clobber logs configuration so must be updated before we send any Log updates - if metadata.ResourceData.HasChange("app_settings") { + if metadata.ResourceData.HasChange("app_settings") || metadata.ResourceData.HasChange("site_config.0.health_check_eviction_time_in_min") { appSettingsUpdate := helpers.ExpandAppSettingsForUpdate(state.AppSettings) + appSettingsUpdate.Properties["WEBSITE_HEALTHCHECK_MAXPINGFAILURES"] = utils.String(strconv.Itoa(state.SiteConfig[0].HealthCheckEvictionTime)) if _, err := client.UpdateApplicationSettings(ctx, id.ResourceGroup, id.SiteName, *appSettingsUpdate); err != nil { return fmt.Errorf("updating App Settings for Windows %s: %+v", id, err) } @@ -778,7 +808,11 @@ func (r WindowsWebAppResource) Update() sdk.ResourceFunc { } if metadata.ResourceData.HasChange("backup") { - backupUpdate := helpers.ExpandBackupConfig(state.Backup) + backupUpdate, err := helpers.ExpandBackupConfig(state.Backup) + if err != nil { + return fmt.Errorf("expanding backup configuration for Windows %s: %+v", *id, err) + } + 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) diff --git a/internal/services/appservice/windows_web_app_resource_test.go b/internal/services/appservice/windows_web_app_resource_test.go index ef8f17d8e75d..eeb0f5ae363a 100644 --- a/internal/services/appservice/windows_web_app_resource_test.go +++ b/internal/services/appservice/windows_web_app_resource_test.go @@ -9,13 +9,12 @@ import ( "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/features" "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" ) -const windowsPythonVersionString = "3.4.0" - type WindowsWebAppResource struct{} func TestAccWindowsWebApp_basic(t *testing.T) { @@ -576,7 +575,7 @@ func TestAccWindowsWebApp_withDotNetCore(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.dotNetCore(data, "core3.1"), + Config: r.dotNetCore(data, "v4.0"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -666,7 +665,7 @@ func TestAccWindowsWebApp_withPython34(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.python(data, windowsPythonVersionString), + Config: r.python(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -688,7 +687,7 @@ func TestAccWindowsWebApp_withPythonUpdate(t *testing.T) { }, data.ImportStep(), { - Config: r.python(data, windowsPythonVersionString), + Config: r.python(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -704,13 +703,58 @@ func TestAccWindowsWebApp_withPythonUpdate(t *testing.T) { }) } -func TestAccWindowsWebApp_withJava8Java(t *testing.T) { +func TestAccWindowsWebApp_withJava8Embedded(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_web_app", "test") + r := WindowsWebAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.java(data, "1.8"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsWebApp_withJava8u322Embedded(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_web_app", "test") + r := WindowsWebAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.java(data, "1.8.0_322"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsWebApp_withJava11Embedded(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_web_app", "test") + r := WindowsWebAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.java(data, "11"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsWebApp_withJava11014Embedded(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_windows_web_app", "test") r := WindowsWebAppResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "1.8", "JAVA", "9.3"), + Config: r.java(data, "11.0.14"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -719,13 +763,13 @@ func TestAccWindowsWebApp_withJava8Java(t *testing.T) { }) } -func TestAccWindowsWebApp_withJava11Java(t *testing.T) { +func TestAccWindowsWebApp_withJava17Embedded(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_windows_web_app", "test") r := WindowsWebAppResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "11", "JAVA", "9.3"), + Config: r.java(data, "17"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -734,13 +778,43 @@ func TestAccWindowsWebApp_withJava11Java(t *testing.T) { }) } -func TestAccWindowsWebApp_withJava17Java(t *testing.T) { +func TestAccWindowsWebApp_withJava1702Embedded(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_windows_web_app", "test") r := WindowsWebAppResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "17", "JAVA", "9.3"), + Config: r.java(data, "17.0.2"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsWebApp_withJava17Tomcat10(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_web_app", "test") + r := WindowsWebAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.javaTomcat(data, "17", "10.0"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsWebApp_withJava110414Tomcat10020(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_web_app", "test") + r := WindowsWebAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.javaTomcat(data, "11.0.14", "10.0.20"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -767,13 +841,16 @@ func TestAccWindowsWebApp_basicDockerContainer(t *testing.T) { // TODO: More Java matrix tests... -func TestAccWindowsWebApp_withNode12lts(t *testing.T) { +func TestAccWindowsWebApp_withNode12(t *testing.T) { + if features.FourPointOh() { + t.Fatalf("Node 12 is no longer supported in v4.0") + } data := acceptance.BuildTestData(t, "azurerm_windows_web_app", "test") r := WindowsWebAppResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.node(data, "12-LTS"), + Config: r.node(data, "~12"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -782,13 +859,28 @@ func TestAccWindowsWebApp_withNode12lts(t *testing.T) { }) } -func TestAccWindowsWebApp_withNode14lts(t *testing.T) { +func TestAccWindowsWebApp_withNode14(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_windows_web_app", "test") r := WindowsWebAppResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.node(data, "14-LTS"), + Config: r.node(data, "~14"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsWebApp_withNode18(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_web_app", "test") + r := WindowsWebAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.node(data, "~18"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -806,6 +898,7 @@ func TestAccWindowsWebApp_withMultiStack(t *testing.T) { Config: r.multiStack(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("site_config.0.application_stack.0.current_stack").HasValue("python"), ), }, data.ImportStep(), @@ -818,7 +911,7 @@ func TestAccWindowsWebApp_updateAppStack(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "11", "JAVA", "9.3"), + Config: r.java(data, "11"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -840,7 +933,7 @@ func TestAccWindowsWebApp_removeAppStack(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "11", "JAVA", "9.3"), + Config: r.java(data, "11"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -2134,8 +2227,8 @@ resource "azurerm_windows_web_app" "test" { site_config { application_stack { - dotnet_version = "%s" - current_stack = "dotnetcore" + dotnet_core_version = "%s" + current_stack = "dotnetcore" } } } @@ -2221,7 +2314,7 @@ resource "azurerm_windows_web_app" "test" { `, r.baseTemplate(data), data.RandomInteger, phpVersion) } -func (r WindowsWebAppResource) python(data acceptance.TestData, pythonVersion string) string { +func (r WindowsWebAppResource) python(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -2237,25 +2330,41 @@ resource "azurerm_windows_web_app" "test" { site_config { application_stack { - python_version = "%s" - current_stack = "python" + python = "%t" + current_stack = "python" } } } -`, r.baseTemplate(data), data.RandomInteger, pythonVersion) +`, r.baseTemplate(data), data.RandomInteger, true) } //nolint:unparam -func (r WindowsWebAppResource) java(data acceptance.TestData, javaVersion string, javaContainer string, javaContainerVersion string) string { - javaContainerStr := "" - if javaContainer != "" { - javaContainerStr = fmt.Sprintf("java_container = %q", javaContainer) - } - javaContainerVersionStr := "" - if javaContainerVersion != "" { - javaContainerVersionStr = fmt.Sprintf("java_container_version = %q", javaContainerVersion) - } +func (r WindowsWebAppResource) java(data acceptance.TestData, javaVersion string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s +resource "azurerm_windows_web_app" "test" { + name = "acctestWA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_plan_id = azurerm_service_plan.test.id + + site_config { + application_stack { + current_stack = "java" + java_version = "%s" + java_embedded_server_enabled = "true" + } + } +} +`, r.baseTemplate(data), data.RandomInteger, javaVersion) +} + +func (r WindowsWebAppResource) javaTomcat(data acceptance.TestData, javaVersion string, tomcatVersion string) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -2271,14 +2380,13 @@ resource "azurerm_windows_web_app" "test" { site_config { application_stack { - current_stack = "java" - java_version = "%s" - %s - %s + current_stack = "java" + java_version = "%s" + tomcat_version = "%s" } } } -`, r.baseTemplate(data), data.RandomInteger, javaVersion, javaContainerStr, javaContainerVersionStr) +`, r.baseTemplate(data), data.RandomInteger, javaVersion, tomcatVersion) } func (r WindowsWebAppResource) multiStack(data acceptance.TestData) string { @@ -2299,14 +2407,16 @@ resource "azurerm_windows_web_app" "test" { application_stack { dotnet_version = "%s" php_version = "%s" - python_version = "%s" + python = "%t" java_version = "%s" java_container = "%s" java_container_version = "%s" + + current_stack = "python" } } } -`, r.baseTemplate(data), data.RandomInteger, "v4.0", "7.4", windowsPythonVersionString, "1.8", "TOMCAT", "9.0") +`, r.baseTemplate(data), data.RandomInteger, "v4.0", "7.4", true, "1.8", "TOMCAT", "9.0") } func (r WindowsWebAppResource) withLogsHttpBlob(data acceptance.TestData) string { diff --git a/internal/services/appservice/windows_web_app_slot_resource.go b/internal/services/appservice/windows_web_app_slot_resource.go index 7312edef7ab4..1201d412a800 100644 --- a/internal/services/appservice/windows_web_app_slot_resource.go +++ b/internal/services/appservice/windows_web_app_slot_resource.go @@ -3,10 +3,12 @@ package appservice import ( "context" "fmt" + "strconv" "strings" "time" "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2021-02-01/web" // nolint: staticcheck + "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/go-azure-helpers/resourcemanager/commonids" "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" @@ -262,6 +264,12 @@ func (r WindowsWebAppSlotResource) Create() sdk.ResourceFunc { return err } + if *currentStack == helpers.CurrentStackNode { + if webAppSlot.AppSettings == nil { + webAppSlot.AppSettings = make(map[string]string, 0) + } + webAppSlot.AppSettings["WEBSITE_NODE_DEFAULT_VERSION"] = webAppSlot.SiteConfig[0].ApplicationStack[0].NodeVersion + } siteConfig.AppSettings = helpers.ExpandAppSettingsForCreate(webAppSlot.AppSettings) expandedIdentity, err := expandIdentity(metadata.ResourceData.Get("identity").([]interface{})) @@ -313,7 +321,10 @@ func (r WindowsWebAppSlotResource) Create() sdk.ResourceFunc { } appSettings := helpers.ExpandAppSettingsForUpdate(webAppSlot.AppSettings) - if appSettings != nil { + if metadata.ResourceData.HasChange("site_config.0.health_check_eviction_time_in_min") { + appSettings.Properties["WEBSITE_HEALTHCHECK_MAXPINGFAILURES"] = utils.String(strconv.Itoa(webAppSlot.SiteConfig[0].HealthCheckEvictionTime)) + } + if len(appSettings.Properties) > 0 { if _, err := client.UpdateApplicationSettingsSlot(ctx, id.ResourceGroup, id.SiteName, *appSettings, id.SlotName); err != nil { return fmt.Errorf("setting App Settings for Windows %s: %+v", id, err) } @@ -335,7 +346,11 @@ func (r WindowsWebAppSlotResource) Create() sdk.ResourceFunc { } } - backupConfig := helpers.ExpandBackupConfig(webAppSlot.Backup) + backupConfig, err := helpers.ExpandBackupConfig(webAppSlot.Backup) + if err != nil { + return fmt.Errorf("expanding backup configuration for Windows %s: %+v", id, err) + } + if backupConfig.BackupRequestProperties != nil { if _, err := client.UpdateBackupConfigurationSlot(ctx, id.ResourceGroup, id.SiteName, *backupConfig, id.SlotName); err != nil { return fmt.Errorf("adding Backup Settings for Windows %s: %+v", id, err) @@ -381,15 +396,15 @@ func (r WindowsWebAppSlotResource) Read() sdk.ResourceFunc { return err } - webApp, err := client.GetSlot(ctx, id.ResourceGroup, id.SiteName, id.SlotName) + webAppSlot, err := client.GetSlot(ctx, id.ResourceGroup, id.SiteName, id.SlotName) if err != nil { - if utils.ResponseWasNotFound(webApp.Response) { + if utils.ResponseWasNotFound(webAppSlot.Response) { return metadata.MarkAsGone(id) } return fmt.Errorf("reading Windows %s: %+v", id, err) } - props := webApp.SiteProperties + props := webAppSlot.SiteProperties if props == nil { return fmt.Errorf("reading properties of Windows %s", id) } @@ -465,11 +480,11 @@ func (r WindowsWebAppSlotResource) Read() sdk.ResourceFunc { Enabled: utils.NormaliseNilableBool(props.Enabled), HttpsOnly: utils.NormaliseNilableBool(props.HTTPSOnly), KeyVaultReferenceIdentityID: utils.NormalizeNilableString(props.KeyVaultReferenceIdentity), - Kind: utils.NormalizeNilableString(webApp.Kind), + Kind: utils.NormalizeNilableString(webAppSlot.Kind), LogsConfig: helpers.FlattenLogsConfig(logsConfig), SiteCredentials: helpers.FlattenSiteCredentials(siteCredentials), StorageAccounts: helpers.FlattenStorageAccounts(storageAccounts), - Tags: tags.ToTypedObject(webApp.Tags), + Tags: tags.ToTypedObject(webAppSlot.Tags), } if subnetId := utils.NormalizeNilableString(props.VirtualNetworkSubnetID); subnetId != "" { @@ -477,7 +492,10 @@ func (r WindowsWebAppSlotResource) Read() sdk.ResourceFunc { } var healthCheckCount *int - state.AppSettings, healthCheckCount = helpers.FlattenAppSettings(appSettings) + state.AppSettings, healthCheckCount, err = helpers.FlattenAppSettings(appSettings) + if err != nil { + return fmt.Errorf("flattening app settings for Windows %s: %+v", id, err) + } if v := props.OutboundIPAddresses; v != nil { state.OutboundIPAddresses = *v @@ -497,6 +515,14 @@ func (r WindowsWebAppSlotResource) Read() sdk.ResourceFunc { state.SiteConfig = helpers.FlattenSiteConfigWindowsAppSlot(webAppSiteConfig.SiteConfig, currentStack, healthCheckCount) + if nodeVer, ok := state.AppSettings["WEBSITE_NODE_DEFAULT_VERSION"]; ok { + if state.SiteConfig[0].ApplicationStack == nil { + state.SiteConfig[0].ApplicationStack = make([]helpers.ApplicationStackWindows, 0) + } + state.SiteConfig[0].ApplicationStack[0].NodeVersion = nodeVer + delete(state.AppSettings, "WEBSITE_NODE_DEFAULT_VERSION") + } + // Zip Deploys are not retrievable, so attempt to get from config. This doesn't matter for imports as an unexpected value here could break the deployment. if deployFile, ok := metadata.ResourceData.Get("zip_deploy_file").(string); ok { state.ZipDeployFile = deployFile @@ -506,7 +532,7 @@ func (r WindowsWebAppSlotResource) Read() sdk.ResourceFunc { return fmt.Errorf("encoding: %+v", err) } - flattenedIdentity, err := flattenIdentity(webApp.Identity) + flattenedIdentity, err := flattenIdentity(webAppSlot.Identity) if err != nil { return fmt.Errorf("flattening `identity`: %+v", err) } @@ -642,8 +668,10 @@ func (r WindowsWebAppSlotResource) Update() sdk.ResourceFunc { } // (@jackofallops) - App Settings can clobber logs configuration so must be updated before we send any Log updates - if metadata.ResourceData.HasChange("app_settings") { + if metadata.ResourceData.HasChange("app_settings") || metadata.ResourceData.HasChange("site_config.0.health_check_eviction_time_in_min") || metadata.ResourceData.HasChange("site_config.0.application_stack.0.node_version") { appSettingsUpdate := helpers.ExpandAppSettingsForUpdate(state.AppSettings) + appSettingsUpdate.Properties["WEBSITE_HEALTHCHECK_MAXPINGFAILURES"] = utils.String(strconv.Itoa(state.SiteConfig[0].HealthCheckEvictionTime)) + appSettingsUpdate.Properties["WEBSITE_NODE_DEFAULT_VERSION"] = pointer.To(state.SiteConfig[0].ApplicationStack[0].NodeVersion) if _, err := client.UpdateApplicationSettingsSlot(ctx, id.ResourceGroup, id.SiteName, *appSettingsUpdate, id.SlotName); err != nil { return fmt.Errorf("updating App Settings for Windows %s: %+v", id, err) } @@ -667,7 +695,11 @@ func (r WindowsWebAppSlotResource) Update() sdk.ResourceFunc { } if metadata.ResourceData.HasChange("backup") { - backupUpdate := helpers.ExpandBackupConfig(state.Backup) + backupUpdate, err := helpers.ExpandBackupConfig(state.Backup) + if err != nil { + return fmt.Errorf("expanding backup configuration for Windows %s: %+v", *id, err) + } + if backupUpdate.BackupRequestProperties == nil { if _, err := client.DeleteBackupConfigurationSlot(ctx, id.ResourceGroup, id.SiteName, id.SlotName); err != nil { return fmt.Errorf("removing Backup Settings for Windows %s: %+v", id, err) diff --git a/internal/services/appservice/windows_web_app_slot_resource_test.go b/internal/services/appservice/windows_web_app_slot_resource_test.go index d87e3c5126c9..aa8fc580e88e 100644 --- a/internal/services/appservice/windows_web_app_slot_resource_test.go +++ b/internal/services/appservice/windows_web_app_slot_resource_test.go @@ -609,7 +609,7 @@ func TestAccWindowsWebAppSlot_withPython(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.python(data, "3.4.0"), + Config: r.python(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -618,13 +618,13 @@ func TestAccWindowsWebAppSlot_withPython(t *testing.T) { }) } -func TestAccWindowsWebAppSlot_withNode12LTS(t *testing.T) { +func TestAccWindowsWebAppSlot_withNode12(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_windows_web_app_slot", "test") r := WindowsWebAppSlotResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.node(data, "12-LTS"), + Config: r.node(data, "~12"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -633,13 +633,13 @@ func TestAccWindowsWebAppSlot_withNode12LTS(t *testing.T) { }) } -func TestAccWindowsWebAppSlot_withNode14LTS(t *testing.T) { +func TestAccWindowsWebAppSlot_withNode14(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_windows_web_app_slot", "test") r := WindowsWebAppSlotResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.node(data, "14-LTS"), + Config: r.node(data, "~14"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -648,13 +648,13 @@ func TestAccWindowsWebAppSlot_withNode14LTS(t *testing.T) { }) } -func TestAccWindowsWebAppSlot_withNode16LTS(t *testing.T) { +func TestAccWindowsWebAppSlot_withNode18(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_windows_web_app_slot", "test") r := WindowsWebAppSlotResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.node(data, "18-LTS"), + Config: r.node(data, "~18"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -663,13 +663,80 @@ func TestAccWindowsWebAppSlot_withNode16LTS(t *testing.T) { }) } -func TestAccWindowsWebAppSlot_withJava8Java(t *testing.T) { +func TestAccWindowsWebAppSlot_withNodeUpdate(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_windows_web_app_slot", "test") r := WindowsWebAppSlotResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "1.8", "JAVA", "9.3"), + Config: r.node(data, "~16"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.node(data, "~18"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsWebAppSlot_withJava8Embedded(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_web_app_slot", "test") + r := WindowsWebAppSlotResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.java(data, "1.8"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsWebAppSlot_withJava11Embedded(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_web_app_slot", "test") + r := WindowsWebAppSlotResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.java(data, "11"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsWebAppSlot_withJava1702Embedded(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_web_app_slot", "test") + r := WindowsWebAppSlotResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.java(data, "17.0.2"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccWindowsWebAppSlot_withJava17Tomcat10(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_web_app_slot", "test") + r := WindowsWebAppSlotResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.javaTomcat(data, "17", "10.0"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -678,13 +745,13 @@ func TestAccWindowsWebAppSlot_withJava8Java(t *testing.T) { }) } -func TestAccWindowsWebAppSlot_withJava11Java(t *testing.T) { +func TestAccWindowsWebAppSlot_withJava11014Tomcat9(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_windows_web_app_slot", "test") r := WindowsWebAppSlotResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.java(data, "11", "JAVA", "9.3"), + Config: r.javaTomcat(data, "11.0.14", "10.0.20"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -1556,7 +1623,7 @@ resource "azurerm_windows_web_app_slot" "test" { `, r.baseTemplate(data), data.RandomInteger, phpVersion) } -func (r WindowsWebAppSlotResource) python(data acceptance.TestData, pythonVersion string) string { +func (r WindowsWebAppSlotResource) python(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -1570,13 +1637,13 @@ resource "azurerm_windows_web_app_slot" "test" { site_config { application_stack { - current_stack = "python" - python_version = "%s" + current_stack = "python" + python = "%t" } } } -`, r.baseTemplate(data), data.RandomInteger, pythonVersion) +`, r.baseTemplate(data), data.RandomInteger, true) } func (r WindowsWebAppSlotResource) node(data acceptance.TestData, nodeVersion string) string { @@ -1601,16 +1668,31 @@ resource "azurerm_windows_web_app_slot" "test" { `, r.baseTemplate(data), data.RandomInteger, nodeVersion) } -func (r WindowsWebAppSlotResource) java(data acceptance.TestData, javaVersion string, javaContainer string, javaContainerVersion string) string { - javaContainerStr := "" - if javaContainer != "" { - javaContainerStr = fmt.Sprintf("java_container = %q", javaContainer) - } - javaContainerVersionStr := "" - if javaContainerVersion != "" { - javaContainerVersionStr = fmt.Sprintf("java_container_version = %q", javaContainerVersion) - } +func (r WindowsWebAppSlotResource) java(data acceptance.TestData, javaVersion string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_windows_web_app_slot" "test" { + name = "acctestWAS-%d" + app_service_id = azurerm_windows_web_app.test.id + + site_config { + application_stack { + current_stack = "java" + java_version = "%s" + java_embedded_server_enabled = "true" + } + } +} + +`, r.baseTemplate(data), data.RandomInteger, javaVersion) +} +func (r WindowsWebAppSlotResource) javaTomcat(data acceptance.TestData, javaVersion string, tomcatVersion string) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -1624,15 +1706,14 @@ resource "azurerm_windows_web_app_slot" "test" { site_config { application_stack { - current_stack = "java" - java_version = "%s" - %s - %s + current_stack = "java" + java_version = "%s" + tomcat_version = "%s" } } } -`, r.baseTemplate(data), data.RandomInteger, javaVersion, javaContainerStr, javaContainerVersionStr) +`, r.baseTemplate(data), data.RandomInteger, javaVersion, tomcatVersion) } func (r WindowsWebAppSlotResource) docker(data acceptance.TestData) string { diff --git a/website/docs/r/linux_web_app.html.markdown b/website/docs/r/linux_web_app.html.markdown index b818ea2b6d76..e969d8fe1ed3 100644 --- a/website/docs/r/linux_web_app.html.markdown +++ b/website/docs/r/linux_web_app.html.markdown @@ -141,13 +141,15 @@ An `application_stack` block supports the following: * `dotnet_version` - (Optional) The version of .NET to use. Possible values include `3.1`, `5.0`, `6.0` and `7.0`. +* `go_version` - (Optional) The version of Go to use. Possible values include `1.18`, and `1.19`. + * `java_server` - (Optional) The Java server type. Possible values include `JAVA`, `TOMCAT`, and `JBOSSEAP`. ~> **NOTE:** `JBOSSEAP` requires a Premium Service Plan SKU to be a valid option. * `java_server_version` - (Optional) The Version of the `java_server` to use. -* `java_version` - (Optional) The Version of Java to use. Supported versions of Java vary depending on the `java_server` and `java_server_version`, as well as security and fixes to major versions. Please see Azure documentation for the latest information. +* `java_version` - (Optional) The Version of Java to use. Possible values include `8`, `11`, and `17`. ~> **NOTE:** The valid version combinations for `java_version`, `java_server` and `java_server_version` can be checked from the command line via `az webapp list-runtimes --linux`. diff --git a/website/docs/r/linux_web_app_slot.html.markdown b/website/docs/r/linux_web_app_slot.html.markdown index 02388c41b68f..424d3b3d8ab7 100644 --- a/website/docs/r/linux_web_app_slot.html.markdown +++ b/website/docs/r/linux_web_app_slot.html.markdown @@ -140,7 +140,9 @@ An `application_stack` block supports the following: * `docker_image_tag` - (Optional) The image Tag to use. e.g. `latest`. -* `dotnet_version` - (Optional) The version of .NET to use. Possible values are `3.1`, `5.0`, `6.0` and `7.0`. +* `dotnet_version` - (Optional) The version of .NET to use. Possible values include `3.1`, `5.0`, `6.0` and `7.0`. + +* `go_version` - (Optional) The version of Go to use. Possible values include `1.18`, and `1.19`. * `java_server` - (Optional) The Java server type. Possible values include `JAVA`, `TOMCAT`, and `JBOSSEAP`. @@ -148,7 +150,7 @@ An `application_stack` block supports the following: * `java_server_version` - (Optional) The Version of the `java_server` to use. -* `java_version` - (Optional) The Version of Java to use. Supported versions of Java vary depending on the `java_server` and `java_server_version`, as well as security and fixes to major versions. Please see Azure documentation for the latest information. +* `java_version` - (Optional) The Version of Java to use. Possible values include `8`, `11`, and `17`. ~> **NOTE:** The valid version combinations for `java_version`, `java_server` and `java_server_version` can be checked from the command line via `az webapp list-runtimes --linux`. diff --git a/website/docs/r/windows_function_app.html.markdown b/website/docs/r/windows_function_app.html.markdown index 4173911a1ad5..736dffbb2d84 100644 --- a/website/docs/r/windows_function_app.html.markdown +++ b/website/docs/r/windows_function_app.html.markdown @@ -146,16 +146,18 @@ An `active_directory` block supports the following: A `application_stack` block supports the following: -* `dotnet_version` - (Optional) The version of .NET to use. Possible values include `3.1`, `6` and `7`. +* `dotnet_version` - (Optional) The version of .NET to use. Possible values include `v3.0`, `v4.0` `v6.0` and `v7.0`. * `use_dotnet_isolated_runtime` - (Optional) Should the DotNet process use an isolated runtime. Defaults to `false`. -* `java_version` - (Optional) The Version of Java to use. Supported versions include `8`, `11` & `17` (In-Preview). +* `java_version` - (Optional) The Version of Java to use. Supported versions include `1.8`, `11` & `17` (In-Preview). * `node_version` - (Optional) The version of Node to run. Possible values include `~12`, `~14`, `~16` and `~18`. * `powershell_core_version` - (Optional) The version of PowerShell Core to run. Possible values are `7`, and `7.2`. +~> **NOTE:** A value of `7` will provide the latest stable version. `7.2` is in preview at the time of writing. + * `use_custom_runtime` - (Optional) Should the Windows Function App use a custom runtime? --- diff --git a/website/docs/r/windows_web_app.html.markdown b/website/docs/r/windows_web_app.html.markdown index 14d4105675a2..351e700d4f5f 100644 --- a/website/docs/r/windows_web_app.html.markdown +++ b/website/docs/r/windows_web_app.html.markdown @@ -138,31 +138,43 @@ An `application_stack` block supports the following: ~> **NOTE:** Whilst this property is Optional omitting it can cause unexpected behaviour, in particular for display of settings in the Azure Portal. -~> **NOTE:** The value of `dotnetcore` is for use in combination with `dotnet_version` set to `core3.1` only. - * `docker_container_name` - (Optional) The name of the Docker Container. For example `azure-app-service/samples/aspnethelloworld` * `docker_container_registry` - (Optional) The registry Host on which the specified Docker Container can be located. For example `mcr.microsoft.com` * `docker_container_tag` - (Optional) The Image Tag of the specified Docker Container to use. For example `latest` -* `dotnet_version` - (Optional) The version of .NET to use when `current_stack` is set to `dotnet`. Possible values include `v2.0`,`v3.0`,`core3.1`, `v4.0`, `v5.0`, `v6.0` and `v7.0`. +* `dotnet_version` - (Optional) The version of .NET to use when `current_stack` is set to `dotnet`. Possible values include `v2.0`,`v3.0`, `v4.0`, `v5.0`, `v6.0` and `v7.0`. + +~> **NOTE:** The Portal displayed values and the actual underlying API values differ for this setting, as follows: +Portal Value | API value +:--|--: +ASP.NET V3.5 | v2.0 +ASP.NET V4.8 | v4.0 +.NET 6 (LTS) | v6.0 +.NET 7 (STS) | v7.0 + +* `dotnet_core_version` - (Optional) The version of .NET to use when `current_stack` is set to `dotnetcore`. Possible values include `v4.0`. -* `java_container` - (Optional) The Java container type to use when `current_stack` is set to `java`. Possible values include `JAVA`, `JETTY`, and `TOMCAT`. Required with `java_version` and `java_container_version`. +* `tomcat_version` - (Optional) The version of Tomcat the Java App should use. Conflicts with `java_embedded_server_enabled` -* `java_container_version` - (Optional) The Version of the `java_container` to use. Required with `java_version` and `java_container`. +~> **NOTE:** See the official documentation for current supported versions. Some example valuess include: `10.0`, `10.0.20`. -* `java_version` - (Optional) The version of Java to use when `current_stack` is set to `java`. Possible values include `1.7`, `1.8`, `11` and `17`. Required with `java_container` and `java_container_version`. +* `java_embedded_server_enabled` - (Optional) Should the Java Embedded Server (Java SE) be used to run the app. -~> **NOTE:** For compatible combinations of `java_version`, `java_container` and `java_container_version` users can use `az webapp list-runtimes` from command line. +* `java_version` - (Optional) The version of Java to use when `current_stack` is set to `java`. -* `node_version` - (Optional) The version of node to use when `current_stack` is set to `node`. Possible values include `12-LTS`, `14-LTS`, `16-LTS` and `18-LTS`. +~> **NOTE:** For currently supported versions, please see the official documentation. Some example values include: `1.8`, `1.8.0_322`, `11`, `11.0.14`, `17` and `17.0.2` + +* `node_version` - (Optional) The version of node to use when `current_stack` is set to `node`. Possible values are `~12`, `~14`, `~16`, and `~18`. ~> **NOTE:** This property conflicts with `java_version`. -* `php_version` - (Optional) The version of PHP to use when `current_stack` is set to `php`. Possible values include `v7.4`. +* `php_version` - (Optional) The version of PHP to use when `current_stack` is set to `php`. Possible values are `v7.1`, `v7.4` and `Off`. + +~> **NOTE:** The value `Off` is used to signify latest supported by the service. -* `python_version` - (Optional) The version of Python to use when `current_stack` is set to `python`. Possible values include `2.7` and `3.4.0`. +* `python` - (Optional) Specifies whether this is a Python app. Defaults to `false`. --- diff --git a/website/docs/r/windows_web_app_slot.html.markdown b/website/docs/r/windows_web_app_slot.html.markdown index c1b30a6ce8e6..6bb89e481725 100644 --- a/website/docs/r/windows_web_app_slot.html.markdown +++ b/website/docs/r/windows_web_app_slot.html.markdown @@ -136,37 +136,41 @@ A `application_logs` block supports the following: --- -A `application_stack` block supports the following: +An `application_stack` block supports the following: -* `current_stack` - (Optional) The Application Stack for the Windows Web App Slot. Possible values include `dotnet`, `dotnetcore`, `node`, `python`, `php`, and `java`. +* `current_stack` - (Optional) The Application Stack for the Windows Web App. Possible values include `dotnet`, `dotnetcore`, `node`, `python`, `php`, and `java`. ~> **NOTE:** Whilst this property is Optional omitting it can cause unexpected behaviour, in particular for display of settings in the Azure Portal. -~> **NOTE:** The value of `dotnetcore` is for use in combination with `dotnet_version` set to `v3.0` only. - * `docker_container_name` - (Optional) The name of the Docker Container. For example `azure-app-service/samples/aspnethelloworld` * `docker_container_registry` - (Optional) The registry Host on which the specified Docker Container can be located. For example `mcr.microsoft.com` * `docker_container_tag` - (Optional) The Image Tag of the specified Docker Container to use. For example `latest` -* `dotnet_version` - (Optional) The version of .NET to use when `current_stack` is set to `dotnet`. Possible values are `v2.0`, `v3.0`, `core3.1`, `v4.0`, `v5.0`, `v6.0` and `v7.0` . +* `dotnet_version` - (Optional) The version of .NET to use when `current_stack` is set to `dotnet`. Possible values include `v2.0`,`v3.0`, `v4.0`, `v5.0`, `v6.0` and `v7.0`. + +* `dotnet_core_version` - (Optional) The version of .NET to use when `current_stack` is set to `dotnetcore`. Possible values include `v4.0`. -* `java_container` - (Optional) The Java container type to use when `current_stack` is set to `java`. Possible values include `JAVA`, `JETTY`, and `TOMCAT`. Required with `java_version` and `java_container_version`. +* `tomcat_version` - (Optional) The version of Tomcat the Java App should use. -* `java_container_version` - (Optional) The Version of the `java_container` to use. Required with `java_version` and `java_container`. +~> **NOTE:** See the official documentation for current supported versions. + +* `java_embedded_server_enabled` - (Optional) Should the Java Embedded Server (Java SE) be used to run the app. * `java_version` - (Optional) The version of Java to use when `current_stack` is set to `java`. Possible values include `1.7`, `1.8`, `11` and `17`. Required with `java_container` and `java_container_version`. ~> **NOTE:** For compatible combinations of `java_version`, `java_container` and `java_container_version` users can use `az webapp list-runtimes` from command line. -* `node_version` - (Optional) The version of node to use when `current_stack` is set to `node`. Possible values include `12-LTS`, `14-LTS`, `16-LTS` and `18-LTS`. +* `node_version` - (Optional) The version of node to use when `current_stack` is set to `node`. Possible values include `~12`, `~14`, `~16`, and `~18`. ~> **NOTE:** This property conflicts with `java_version`. -* `php_version` - (Optional) The version of PHP to use when `current_stack` is set to `php`. Possible values include `v7.4`. +* `php_version` - (Optional) The version of PHP to use when `current_stack` is set to `php`. Possible values include `v7.4` and `Off`. + +~> **NOTE:** The value `Off` is used to signify latest supported by the service. -* `python_version` - (Optional) The version of Python to use when `current_stack` is set to `python`. Possible values include `2.7` and `3.4.0`. +* `python` - (Optional) The app is a Python app. Defaults to `false`. ---