diff --git a/internal/services/appservice/helpers/app_stack.go b/internal/services/appservice/helpers/app_stack.go index 177e3c9414f4..834b9fe85333 100644 --- a/internal/services/appservice/helpers/app_stack.go +++ b/internal/services/appservice/helpers/app_stack.go @@ -55,7 +55,7 @@ func windowsApplicationStackSchema() *pluginsdk.Schema { Type: pluginsdk.TypeString, Optional: true, Computed: true, - ValidateFunc: validation.StringInSlice([]string{ + 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", @@ -508,7 +508,7 @@ func linuxApplicationStackSchema() *pluginsdk.Schema { Optional: true, ValidateFunc: validation.StringInSlice([]string{ // TODO replace with major.minor regex? "2.6", // Deprecated - accepted but not offered in the portal. Remove in 4.0 - "2.7", + "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", diff --git a/internal/services/appservice/helpers/auto_heal.go b/internal/services/appservice/helpers/auto_heal.go index 33ce9a8086ce..e9ba58e5fc64 100644 --- a/internal/services/appservice/helpers/auto_heal.go +++ b/internal/services/appservice/helpers/auto_heal.go @@ -6,6 +6,7 @@ import ( "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" ) @@ -194,9 +195,9 @@ func autoHealTriggerSchemaWindows() *pluginsdk.Schema { }, "interval": { - Type: pluginsdk.TypeString, - Required: true, - // ValidateFunc: validation.IsRFC3339Time, // TODO should be hh:mm:ss - This is too loose, need to improve + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validate.AutoHealInterval, // TODO should be hh:mm:ss - This is too loose, need to improve }, }, }, @@ -216,7 +217,7 @@ func autoHealTriggerSchemaWindows() *pluginsdk.Schema { "status_code_range": { Type: pluginsdk.TypeString, Required: true, - ValidateFunc: nil, // TODO - status code range validation + ValidateFunc: validate.StatusCodeRange, }, "count": { @@ -226,21 +227,19 @@ func autoHealTriggerSchemaWindows() *pluginsdk.Schema { }, "interval": { - Type: pluginsdk.TypeString, - Required: true, - // ValidateFunc: validation.IsRFC3339Time, + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validate.AutoHealInterval, }, "sub_status": { - Type: pluginsdk.TypeInt, - Optional: true, - ValidateFunc: nil, // TODO - no docs on this, needs investigation + Type: pluginsdk.TypeInt, + Optional: true, }, "win32_status": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: nil, // TODO - no docs on this, needs investigation + Type: pluginsdk.TypeString, + Optional: true, }, "path": { @@ -259,15 +258,15 @@ func autoHealTriggerSchemaWindows() *pluginsdk.Schema { Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ "time_taken": { - Type: pluginsdk.TypeString, - Required: true, - // ValidateFunc: validation.IsRFC3339Time, + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validate.AutoHealInterval, }, "interval": { - Type: pluginsdk.TypeString, - Required: true, - // ValidateFunc: validation.IsRFC3339Time, + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validate.AutoHealInterval, }, "count": { diff --git a/internal/services/appservice/helpers/common_web_app_schema.go b/internal/services/appservice/helpers/common_web_app_schema.go index 4e288cad3eed..f9768de89dcc 100644 --- a/internal/services/appservice/helpers/common_web_app_schema.go +++ b/internal/services/appservice/helpers/common_web_app_schema.go @@ -1,6 +1,7 @@ package helpers import ( + "fmt" "strconv" "time" @@ -344,10 +345,11 @@ func BackupSchema() *pluginsdk.Schema { }, "start_time": { - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - Description: "When the schedule should start working in RFC-3339 format.", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + Description: "When the schedule should start working in RFC-3339 format.", + ValidateFunc: validation.IsRFC3339Time, }, "last_execution_time": { @@ -872,10 +874,10 @@ func ExpandLogsConfig(config []LogsConfig) *web.SiteLogsConfig { return result } -func ExpandBackupConfig(backupConfigs []Backup) *web.BackupRequest { +func ExpandBackupConfig(backupConfigs []Backup) (*web.BackupRequest, error) { result := &web.BackupRequest{} if len(backupConfigs) == 0 { - return result + return result, nil } backupConfig := backupConfigs[0] @@ -893,11 +895,14 @@ func ExpandBackupConfig(backupConfigs []Backup) *web.BackupRequest { } if backupSchedule.StartTime != "" { - dateTimeToStart, _ := time.Parse(time.RFC3339, 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 + return result, nil } func ExpandStorageConfig(storageConfigs []StorageAccount) *web.AzureStoragePropertyDictionaryResource { @@ -1008,7 +1013,7 @@ func expandVirtualApplicationsForUpdate(virtualApplicationConfig []VirtualApplic func FlattenBackupConfig(backupRequest web.BackupRequest) []Backup { if backupRequest.BackupRequestProperties == nil { - return nil + return []Backup{} } props := *backupRequest.BackupRequestProperties backup := Backup{} @@ -1056,7 +1061,7 @@ func FlattenBackupConfig(backupRequest web.BackupRequest) []Backup { func FlattenLogsConfig(logsConfig web.SiteLogsConfig) []LogsConfig { if logsConfig.SiteLogsConfigProperties == nil { - return nil + return []LogsConfig{} } props := *logsConfig.SiteLogsConfigProperties if onlyDefaultLoggingConfig(props) { @@ -1163,7 +1168,7 @@ func onlyDefaultLoggingConfig(props web.SiteLogsConfigProperties) bool { func FlattenStorageAccounts(appStorageAccounts web.AzureStoragePropertyDictionaryResource) []StorageAccount { if len(appStorageAccounts.Properties) == 0 { - return nil + return []StorageAccount{} } var storageAccounts []StorageAccount for k, v := range appStorageAccounts.Properties { @@ -1195,7 +1200,7 @@ func FlattenStorageAccounts(appStorageAccounts web.AzureStoragePropertyDictionar func FlattenConnectionStrings(appConnectionStrings web.ConnectionStringDictionary) []ConnectionString { if len(appConnectionStrings.Properties) == 0 { - return nil + return []ConnectionString{} } var connectionStrings []ConnectionString for k, v := range appConnectionStrings.Properties { @@ -1237,7 +1242,7 @@ func ExpandAppSettingsForCreate(settings map[string]string) *[]web.NameValuePair return nil } -func FlattenAppSettings(input web.StringDictionary) (map[string]string, *int) { +func FlattenAppSettings(input web.StringDictionary) (map[string]string, *int, error) { maxPingFailures := "WEBSITE_HEALTHCHECK_MAXPINGFAILURES" unmanagedSettings := []string{ "DIAGNOSTICS_AZUREBLOBCONTAINERSASURL", @@ -1254,7 +1259,10 @@ func FlattenAppSettings(input web.StringDictionary) (map[string]string, *int) { var healthCheckCount *int appSettings := FlattenWebStringDictionary(input) if v, ok := appSettings[maxPingFailures]; ok { - h, _ := strconv.Atoi(v) + h, err := strconv.Atoi(v) + if err != nil { + return nil, nil, fmt.Errorf("could not convert max ping failures to int") + } healthCheckCount = &h } @@ -1263,7 +1271,7 @@ func FlattenAppSettings(input web.StringDictionary) (map[string]string, *int) { delete(appSettings, v) } - return appSettings, healthCheckCount + return appSettings, healthCheckCount, nil } func flattenVirtualApplications(appVirtualApplications *[]web.VirtualApplication) []VirtualApplication { diff --git a/internal/services/appservice/helpers/function_app_slot_schema.go b/internal/services/appservice/helpers/function_app_slot_schema.go index 74a635f858cd..25995745a9b7 100644 --- a/internal/services/appservice/helpers/function_app_slot_schema.go +++ b/internal/services/appservice/helpers/function_app_slot_schema.go @@ -921,7 +921,7 @@ func FlattenSiteConfigWindowsFunctionAppSlot(functionAppSlotSiteConfig *web.Site NodeVersion: "", // Note: this will be set from app_settings later in unpackWindowsFunctionAppSettings JavaVersion: pointer.From(functionAppSlotSiteConfig.JavaVersion), PowerShellCoreVersion: powershellVersion, - CustomHandler: false, // set this later from app_settings + CustomHandler: false, // Note: this is set later from app_settings }} return result, nil diff --git a/internal/services/appservice/helpers/web_app_slot_schema.go b/internal/services/appservice/helpers/web_app_slot_schema.go index 0a1f25f03a0f..30ab1b674cfe 100644 --- a/internal/services/appservice/helpers/web_app_slot_schema.go +++ b/internal/services/appservice/helpers/web_app_slot_schema.go @@ -846,7 +846,7 @@ func ExpandSiteConfigWindowsWebAppSlot(siteConfig []SiteConfigWindowsWebAppSlot, } } if winAppStack.NetCoreVersion != "" { - expanded.NetFrameworkVersion = pointer.To(winAppStack.NetFrameworkVersion) + expanded.NetFrameworkVersion = pointer.To(winAppStack.NetCoreVersion) if currentStack == "" { currentStack = CurrentStackDotNetCore } diff --git a/internal/services/appservice/helpers/windows_web_app_schema.go b/internal/services/appservice/helpers/windows_web_app_schema.go index 8769aae4a57b..48b0d423e5a3 100644 --- a/internal/services/appservice/helpers/windows_web_app_schema.go +++ b/internal/services/appservice/helpers/windows_web_app_schema.go @@ -7,6 +7,7 @@ import ( "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" @@ -461,6 +462,8 @@ func ExpandSiteConfigWindows(siteConfig []SiteConfigWindows, existing *web.SiteC 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") } } @@ -486,7 +489,6 @@ func ExpandSiteConfigWindows(siteConfig []SiteConfigWindows, existing *web.SiteC if metadata.ResourceData.HasChange("site_config.0.application_stack") { if len(winSiteConfig.ApplicationStack) == 1 { winAppStack := winSiteConfig.ApplicationStack[0] - // TODO - only one of these should be non-nil? if winAppStack.NetFrameworkVersion != "" { expanded.NetFrameworkVersion = pointer.To(winAppStack.NetFrameworkVersion) if currentStack == "" { @@ -572,7 +574,7 @@ func ExpandSiteConfigWindows(siteConfig []SiteConfigWindows, existing *web.SiteC if metadata.ResourceData.HasChange("site_config.0.ip_restriction") { ipRestrictions, err := ExpandIpRestrictions(winSiteConfig.IpRestriction) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("expanding IP Restrictions: %+v", err) } expanded.IPSecurityRestrictions = ipRestrictions } @@ -582,7 +584,7 @@ func ExpandSiteConfigWindows(siteConfig []SiteConfigWindows, existing *web.SiteC if metadata.ResourceData.HasChange("site_config.0.scm_ip_restriction") { scmIpRestrictions, err := ExpandIpRestrictions(winSiteConfig.ScmIpRestriction) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("expanding SCM IP Restrictions: %+v", err) } expanded.ScmIPSecurityRestrictions = scmIpRestrictions } @@ -652,9 +654,9 @@ func ExpandSiteConfigWindows(siteConfig []SiteConfigWindows, existing *web.SiteC return expanded, ¤tStack, nil } -func FlattenSiteConfigWindows(appSiteConfig *web.SiteConfig, currentStack string, healthCheckCount *int) []SiteConfigWindows { +func FlattenSiteConfigWindows(appSiteConfig *web.SiteConfig, currentStack string, healthCheckCount *int) ([]SiteConfigWindows, error) { if appSiteConfig == nil { - return nil + return nil, nil } siteConfig := SiteConfigWindows{ @@ -688,7 +690,11 @@ func FlattenSiteConfigWindows(appSiteConfig *web.SiteConfig, currentStack string } if appSiteConfig.APIManagementConfig != nil && appSiteConfig.APIManagementConfig.ID != nil { - siteConfig.ApiManagementConfigId = *appSiteConfig.APIManagementConfig.ID + 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 { @@ -763,5 +769,5 @@ func FlattenSiteConfigWindows(appSiteConfig *web.SiteConfig, currentStack string } } - return []SiteConfigWindows{siteConfig} + return []SiteConfigWindows{siteConfig}, nil } 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_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_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 e803feb149b3..167a858cd5ba 100644 --- a/internal/services/appservice/linux_web_app_resource.go +++ b/internal/services/appservice/linux_web_app_resource.go @@ -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 @@ -757,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_slot_resource.go b/internal/services/appservice/linux_web_app_slot_resource.go index 8500f9429c07..3a6f8c6dd629 100644 --- a/internal/services/appservice/linux_web_app_slot_resource.go +++ b/internal/services/appservice/linux_web_app_slot_resource.go @@ -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 @@ -657,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/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 a8813132e173..0e34b0a44650 100644 --- a/internal/services/appservice/windows_function_app_resource.go +++ b/internal/services/appservice/windows_function_app_resource.go @@ -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) @@ -900,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) diff --git a/internal/services/appservice/windows_function_app_slot_resource.go b/internal/services/appservice/windows_function_app_slot_resource.go index b93a2dd025d8..7576bf042e9e 100644 --- a/internal/services/appservice/windows_function_app_slot_resource.go +++ b/internal/services/appservice/windows_function_app_slot_resource.go @@ -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) @@ -846,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) 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 a6ffa0aa6f3b..cf2de3f4ebab 100644 --- a/internal/services/appservice/windows_web_app_resource.go +++ b/internal/services/appservice/windows_web_app_resource.go @@ -401,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) @@ -551,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 @@ -569,8 +576,10 @@ 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) @@ -799,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 037a7fb4c992..eeb0f5ae363a 100644 --- a/internal/services/appservice/windows_web_app_resource_test.go +++ b/internal/services/appservice/windows_web_app_resource_test.go @@ -3,13 +3,13 @@ package appservice_test import ( "context" "fmt" - "github.com/hashicorp/terraform-provider-azurerm/internal/features" "regexp" "testing" "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/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" diff --git a/internal/services/appservice/windows_web_app_slot_resource.go b/internal/services/appservice/windows_web_app_slot_resource.go index 83ca356dba1a..1201d412a800 100644 --- a/internal/services/appservice/windows_web_app_slot_resource.go +++ b/internal/services/appservice/windows_web_app_slot_resource.go @@ -346,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) @@ -488,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 @@ -688,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)