diff --git a/azurerm/helpers/azure/app_service.go b/azurerm/helpers/azure/app_service.go deleted file mode 100644 index 5291dff40210..000000000000 --- a/azurerm/helpers/azure/app_service.go +++ /dev/null @@ -1,1939 +0,0 @@ -package azure - -import ( - "fmt" - "log" - "regexp" - "strings" - - "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2019-08-01/web" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/helper/validation" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" -) - -const ( - // TODO: switch back once https://github.com/Azure/azure-rest-api-specs/pull/8435 has been fixed - SystemAssignedUserAssigned web.ManagedServiceIdentityType = "SystemAssigned, UserAssigned" -) - -func SchemaAppServiceAadAuthSettings() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "client_id": { - Type: schema.TypeString, - Required: true, - }, - "client_secret": { - Type: schema.TypeString, - Optional: true, - Sensitive: true, - }, - "allowed_audiences": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - }, - } -} - -func SchemaAppServiceFacebookAuthSettings() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "app_id": { - Type: schema.TypeString, - Required: true, - }, - "app_secret": { - Type: schema.TypeString, - Required: true, - Sensitive: true, - }, - "oauth_scopes": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - }, - } -} - -func SchemaAppServiceGoogleAuthSettings() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "client_id": { - Type: schema.TypeString, - Required: true, - }, - "client_secret": { - Type: schema.TypeString, - Required: true, - Sensitive: true, - }, - "oauth_scopes": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - }, - } -} - -func SchemaAppServiceMicrosoftAuthSettings() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "client_id": { - Type: schema.TypeString, - Required: true, - }, - "client_secret": { - Type: schema.TypeString, - Required: true, - Sensitive: true, - }, - "oauth_scopes": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - }, - } -} - -func SchemaAppServiceTwitterAuthSettings() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "consumer_key": { - Type: schema.TypeString, - Required: true, - }, - "consumer_secret": { - Type: schema.TypeString, - Required: true, - Sensitive: true, - }, - }, - }, - } -} - -func SchemaAppServiceAuthSettings() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "enabled": { - Type: schema.TypeBool, - Required: true, - }, - "additional_login_params": { - Type: schema.TypeMap, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "allowed_external_redirect_urls": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "default_provider": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.AzureActiveDirectory), - string(web.Facebook), - string(web.Google), - string(web.MicrosoftAccount), - string(web.Twitter), - }, false), - }, - "issuer": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.IsURLWithScheme([]string{"http", "https"}), - }, - "runtime_version": { - Type: schema.TypeString, - Optional: true, - }, - "token_refresh_extension_hours": { - Type: schema.TypeFloat, - Optional: true, - Default: 72, - }, - "token_store_enabled": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "unauthenticated_client_action": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.AllowAnonymous), - string(web.RedirectToLoginPage), - }, false), - }, - "active_directory": SchemaAppServiceAadAuthSettings(), - "facebook": SchemaAppServiceFacebookAuthSettings(), - "google": SchemaAppServiceGoogleAuthSettings(), - "microsoft": SchemaAppServiceMicrosoftAuthSettings(), - "twitter": SchemaAppServiceTwitterAuthSettings(), - }, - }, - } -} - -func SchemaAppServiceIdentity() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.ManagedServiceIdentityTypeNone), - string(web.ManagedServiceIdentityTypeSystemAssigned), - string(SystemAssignedUserAssigned), - string(web.ManagedServiceIdentityTypeUserAssigned), - }, true), - DiffSuppressFunc: suppress.CaseDifference, - }, - "principal_id": { - Type: schema.TypeString, - Computed: true, - }, - "tenant_id": { - Type: schema.TypeString, - Computed: true, - }, - "identity_ids": { - Type: schema.TypeList, - Optional: true, - MinItems: 1, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.NoZeroValues, - }, - }, - }, - }, - } -} - -func SchemaAppServiceSiteConfig() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "always_on": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - - "app_command_line": { - Type: schema.TypeString, - Optional: true, - }, - - "default_documents": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - - "dotnet_framework_version": { - Type: schema.TypeString, - Optional: true, - Default: "v4.0", - ValidateFunc: validation.StringInSlice([]string{ - "v2.0", - "v4.0", - }, true), - DiffSuppressFunc: suppress.CaseDifference, - }, - - "http2_enabled": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - - "ip_restriction": { - Type: schema.TypeList, - Optional: true, - Computed: true, - ConfigMode: schema.SchemaConfigModeAttr, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "ip_address": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validate.CIDR, - }, - "virtual_network_subnet_id": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "name": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "priority": { - Type: schema.TypeInt, - Optional: true, - Default: 65000, - ValidateFunc: validation.IntBetween(1, 2147483647), - }, - "action": { - Type: schema.TypeString, - Default: "Allow", - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - "Allow", - "Deny", - }, false), - }, - }, - }, - }, - - "scm_use_main_ip_restriction": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - - "scm_ip_restriction": { - Type: schema.TypeList, - Optional: true, - Computed: true, - ConfigMode: schema.SchemaConfigModeAttr, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "ip_address": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validate.CIDR, - }, - "virtual_network_subnet_id": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "name": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "priority": { - Type: schema.TypeInt, - Optional: true, - Default: 65000, - ValidateFunc: validation.IntBetween(1, 2147483647), - }, - "action": { - Type: schema.TypeString, - Optional: true, - Default: "Allow", - ValidateFunc: validation.StringInSlice([]string{ - "Allow", - "Deny", - }, true), - }, - }, - }, - }, - - "java_version": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringMatch( - regexp.MustCompile(`^(1\.7|1\.8|11)`), - `Invalid Java version provided`), - }, - - "java_container": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - "JAVA", - "JETTY", - "TOMCAT", - }, true), - DiffSuppressFunc: suppress.CaseDifference, - }, - - "java_container_version": { - Type: schema.TypeString, - Optional: true, - }, - - "local_mysql_enabled": { - Type: schema.TypeBool, - Optional: true, - Computed: true, - }, - - "managed_pipeline_mode": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.Classic), - string(web.Integrated), - }, true), - DiffSuppressFunc: suppress.CaseDifference, - }, - - "php_version": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - "5.5", - "5.6", - "7.0", - "7.1", - "7.2", - "7.3", - }, false), - }, - - "python_version": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - "2.7", - "3.4", - }, false), - }, - - "remote_debugging_enabled": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - - "remote_debugging_version": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice([]string{ - "VS2012", // TODO for 3.0 - remove VS2012, VS2013, VS2015 - "VS2013", - "VS2015", - "VS2017", - "VS2019", - }, true), - DiffSuppressFunc: suppress.CaseDifference, - }, - - "scm_type": { - Type: schema.TypeString, - Optional: true, - Default: string(web.ScmTypeNone), - ValidateFunc: validation.StringInSlice([]string{ - string(web.ScmTypeBitbucketGit), - string(web.ScmTypeBitbucketHg), - string(web.ScmTypeCodePlexGit), - string(web.ScmTypeCodePlexHg), - string(web.ScmTypeDropbox), - string(web.ScmTypeExternalGit), - string(web.ScmTypeExternalHg), - string(web.ScmTypeGitHub), - string(web.ScmTypeLocalGit), - string(web.ScmTypeNone), - string(web.ScmTypeOneDrive), - string(web.ScmTypeTfs), - string(web.ScmTypeVSO), - // Not in the specs, but is set by Azure Pipelines - // https://github.com/Microsoft/azure-pipelines-tasks/blob/master/Tasks/AzureRmWebAppDeploymentV4/operations/AzureAppServiceUtility.ts#L19 - // upstream issue: https://github.com/Azure/azure-rest-api-specs/issues/5345 - "VSTSRM", - }, false), - }, - - "use_32_bit_worker_process": { - Type: schema.TypeBool, - Optional: true, - }, - - "websockets_enabled": { - Type: schema.TypeBool, - Optional: true, - Computed: true, - }, - - "ftps_state": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.AllAllowed), - string(web.Disabled), - string(web.FtpsOnly), - }, false), - }, - - "health_check_path": { - Type: schema.TypeString, - Optional: true, - }, - - "linux_fx_version": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - - "windows_fx_version": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - - "min_tls_version": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.OneFullStopZero), - string(web.OneFullStopOne), - string(web.OneFullStopTwo), - }, false), - }, - - "cors": SchemaWebCorsSettings(), - - "auto_swap_slot_name": { - Type: schema.TypeString, - Optional: true, - }, - }, - }, - } -} - -func SchemaAppServiceLogsConfig() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "application_logs": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "file_system_level": { - Type: schema.TypeString, - Optional: true, - Default: "Off", - ConflictsWith: []string{"logs.0.http_logs.0.azure_blob_storage"}, - ValidateFunc: validation.StringInSlice([]string{ - string(web.Error), - string(web.Information), - string(web.Off), - string(web.Verbose), - string(web.Warning), - }, false), - }, - "azure_blob_storage": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "level": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.Error), - string(web.Information), - string(web.Off), - string(web.Verbose), - string(web.Warning), - }, false), - }, - "sas_url": { - Type: schema.TypeString, - Required: true, - Sensitive: true, - }, - "retention_in_days": { - Type: schema.TypeInt, - Required: true, - }, - }, - }, - }, - }, - }, - }, - "http_logs": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "file_system": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "retention_in_mb": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(25, 100), - }, - "retention_in_days": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntAtLeast(0), - }, - }, - }, - ConflictsWith: []string{"logs.0.http_logs.0.azure_blob_storage"}, - }, - "azure_blob_storage": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "sas_url": { - Type: schema.TypeString, - Required: true, - Sensitive: true, - }, - "retention_in_days": { - Type: schema.TypeInt, - Required: true, - }, - }, - }, - ConflictsWith: []string{"logs.0.http_logs.0.file_system"}, - }, - }, - }, - }, - }, - }, - } -} - -func SchemaAppServiceStorageAccounts() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.AzureBlob), - string(web.AzureFiles), - }, false), - }, - - "account_name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "share_name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "access_key": { - Type: schema.TypeString, - Required: true, - Sensitive: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "mount_path": { - Type: schema.TypeString, - Optional: true, - }, - }, - }, - } -} - -func SchemaAppServiceDataSourceSiteConfig() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "always_on": { - Type: schema.TypeBool, - Computed: true, - }, - - "app_command_line": { - Type: schema.TypeString, - Computed: true, - }, - - "default_documents": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - - "dotnet_framework_version": { - Type: schema.TypeString, - Computed: true, - }, - - "http2_enabled": { - Type: schema.TypeBool, - Computed: true, - }, - - "ip_restriction": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "ip_address": { - Type: schema.TypeString, - Computed: true, - }, - "virtual_network_subnet_id": { - Type: schema.TypeString, - Computed: true, - }, - "name": { - Type: schema.TypeString, - Computed: true, - }, - "priority": { - Type: schema.TypeInt, - Computed: true, - }, - "action": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - - "scm_use_main_ip_restriction": { - Type: schema.TypeBool, - Computed: true, - }, - - "scm_ip_restriction": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "ip_address": { - Type: schema.TypeString, - Computed: true, - }, - "virtual_network_subnet_id": { - Type: schema.TypeString, - Computed: true, - }, - "name": { - Type: schema.TypeString, - Computed: true, - }, - "priority": { - Type: schema.TypeInt, - Computed: true, - }, - "action": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - - "java_version": { - Type: schema.TypeString, - Computed: true, - }, - - "java_container": { - Type: schema.TypeString, - Computed: true, - }, - - "java_container_version": { - Type: schema.TypeString, - Computed: true, - }, - - "local_mysql_enabled": { - Type: schema.TypeBool, - Computed: true, - }, - - "managed_pipeline_mode": { - Type: schema.TypeString, - Computed: true, - }, - - "php_version": { - Type: schema.TypeString, - Computed: true, - }, - - "python_version": { - Type: schema.TypeString, - Computed: true, - }, - - "remote_debugging_enabled": { - Type: schema.TypeBool, - Computed: true, - }, - - "remote_debugging_version": { - Type: schema.TypeString, - Computed: true, - }, - - "scm_type": { - Type: schema.TypeString, - Computed: true, - }, - - "use_32_bit_worker_process": { - Type: schema.TypeBool, - Computed: true, - }, - - "websockets_enabled": { - Type: schema.TypeBool, - Computed: true, - }, - - "ftps_state": { - Type: schema.TypeString, - Computed: true, - }, - - "health_check_path": { - Type: schema.TypeString, - Computed: true, - }, - - "linux_fx_version": { - Type: schema.TypeString, - Computed: true, - }, - - "windows_fx_version": { - Type: schema.TypeString, - Computed: true, - }, - - "min_tls_version": { - Type: schema.TypeString, - Computed: true, - }, - - "cors": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "allowed_origins": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "support_credentials": { - Type: schema.TypeBool, - Computed: true, - }, - }, - }, - }, - }, - }, - } -} - -func ExpandAppServiceAuthSettings(input []interface{}) web.SiteAuthSettingsProperties { - siteAuthSettingsProperties := web.SiteAuthSettingsProperties{} - - if len(input) == 0 { - return siteAuthSettingsProperties - } - - setting := input[0].(map[string]interface{}) - - if v, ok := setting["enabled"]; ok { - siteAuthSettingsProperties.Enabled = utils.Bool(v.(bool)) - } - - if v, ok := setting["additional_login_params"]; ok { - input := v.(map[string]interface{}) - - additionalLoginParams := make([]string, 0) - for k, v := range input { - additionalLoginParams = append(additionalLoginParams, fmt.Sprintf("%s=%s", k, v.(string))) - } - - siteAuthSettingsProperties.AdditionalLoginParams = &additionalLoginParams - } - - if v, ok := setting["allowed_external_redirect_urls"]; ok { - input := v.([]interface{}) - - allowedExternalRedirectUrls := make([]string, 0) - for _, param := range input { - allowedExternalRedirectUrls = append(allowedExternalRedirectUrls, param.(string)) - } - - siteAuthSettingsProperties.AllowedExternalRedirectUrls = &allowedExternalRedirectUrls - } - - if v, ok := setting["default_provider"]; ok { - siteAuthSettingsProperties.DefaultProvider = web.BuiltInAuthenticationProvider(v.(string)) - } - - if v, ok := setting["issuer"]; ok { - siteAuthSettingsProperties.Issuer = utils.String(v.(string)) - } - - if v, ok := setting["runtime_version"]; ok { - siteAuthSettingsProperties.RuntimeVersion = utils.String(v.(string)) - } - - if v, ok := setting["token_refresh_extension_hours"]; ok { - siteAuthSettingsProperties.TokenRefreshExtensionHours = utils.Float(v.(float64)) - } - - if v, ok := setting["token_store_enabled"]; ok { - siteAuthSettingsProperties.TokenStoreEnabled = utils.Bool(v.(bool)) - } - - if v, ok := setting["unauthenticated_client_action"]; ok { - siteAuthSettingsProperties.UnauthenticatedClientAction = web.UnauthenticatedClientAction(v.(string)) - } - - if v, ok := setting["active_directory"]; ok { - activeDirectorySettings := v.([]interface{}) - - for _, setting := range activeDirectorySettings { - if setting == nil { - continue - } - - activeDirectorySetting := setting.(map[string]interface{}) - - if v, ok := activeDirectorySetting["client_id"]; ok { - siteAuthSettingsProperties.ClientID = utils.String(v.(string)) - } - - if v, ok := activeDirectorySetting["client_secret"]; ok { - siteAuthSettingsProperties.ClientSecret = utils.String(v.(string)) - } - - if v, ok := activeDirectorySetting["allowed_audiences"]; ok { - input := v.([]interface{}) - - allowedAudiences := make([]string, 0) - for _, param := range input { - allowedAudiences = append(allowedAudiences, param.(string)) - } - - siteAuthSettingsProperties.AllowedAudiences = &allowedAudiences - } - } - } - - if v, ok := setting["facebook"]; ok { - facebookSettings := v.([]interface{}) - - for _, setting := range facebookSettings { - facebookSetting := setting.(map[string]interface{}) - - if v, ok := facebookSetting["app_id"]; ok { - siteAuthSettingsProperties.FacebookAppID = utils.String(v.(string)) - } - - if v, ok := facebookSetting["app_secret"]; ok { - siteAuthSettingsProperties.FacebookAppSecret = utils.String(v.(string)) - } - - if v, ok := facebookSetting["oauth_scopes"]; ok { - input := v.([]interface{}) - - oauthScopes := make([]string, 0) - for _, param := range input { - oauthScopes = append(oauthScopes, param.(string)) - } - - siteAuthSettingsProperties.FacebookOAuthScopes = &oauthScopes - } - } - } - - if v, ok := setting["google"]; ok { - googleSettings := v.([]interface{}) - - for _, setting := range googleSettings { - googleSetting := setting.(map[string]interface{}) - - if v, ok := googleSetting["client_id"]; ok { - siteAuthSettingsProperties.GoogleClientID = utils.String(v.(string)) - } - - if v, ok := googleSetting["client_secret"]; ok { - siteAuthSettingsProperties.GoogleClientSecret = utils.String(v.(string)) - } - - if v, ok := googleSetting["oauth_scopes"]; ok { - input := v.([]interface{}) - - oauthScopes := make([]string, 0) - for _, param := range input { - oauthScopes = append(oauthScopes, param.(string)) - } - - siteAuthSettingsProperties.GoogleOAuthScopes = &oauthScopes - } - } - } - - if v, ok := setting["microsoft"]; ok { - microsoftSettings := v.([]interface{}) - - for _, setting := range microsoftSettings { - microsoftSetting := setting.(map[string]interface{}) - - if v, ok := microsoftSetting["client_id"]; ok { - siteAuthSettingsProperties.MicrosoftAccountClientID = utils.String(v.(string)) - } - - if v, ok := microsoftSetting["client_secret"]; ok { - siteAuthSettingsProperties.MicrosoftAccountClientSecret = utils.String(v.(string)) - } - - if v, ok := microsoftSetting["oauth_scopes"]; ok { - input := v.([]interface{}) - - oauthScopes := make([]string, 0) - for _, param := range input { - oauthScopes = append(oauthScopes, param.(string)) - } - - siteAuthSettingsProperties.MicrosoftAccountOAuthScopes = &oauthScopes - } - } - } - - if v, ok := setting["twitter"]; ok { - twitterSettings := v.([]interface{}) - - for _, setting := range twitterSettings { - twitterSetting := setting.(map[string]interface{}) - - if v, ok := twitterSetting["consumer_key"]; ok { - siteAuthSettingsProperties.TwitterConsumerKey = utils.String(v.(string)) - } - - if v, ok := twitterSetting["consumer_secret"]; ok { - siteAuthSettingsProperties.TwitterConsumerSecret = utils.String(v.(string)) - } - } - } - - return siteAuthSettingsProperties -} - -func FlattenAdditionalLoginParams(input *[]string) map[string]interface{} { - result := make(map[string]interface{}) - - if input == nil { - return result - } - - for _, k := range *input { - parts := strings.Split(k, "=") - if len(parts) != 2 { - continue // Params not following the format `key=value` is considered malformed and will be ignored. - } - key := parts[0] - value := parts[1] - - result[key] = value - } - - return result -} - -func FlattenAppServiceAuthSettings(input *web.SiteAuthSettingsProperties) []interface{} { - results := make([]interface{}, 0) - if input == nil { - return results - } - - result := make(map[string]interface{}) - - if input.Enabled != nil { - result["enabled"] = *input.Enabled - } - - result["additional_login_params"] = FlattenAdditionalLoginParams(input.AdditionalLoginParams) - - allowedExternalRedirectUrls := make([]string, 0) - if s := input.AllowedExternalRedirectUrls; s != nil { - allowedExternalRedirectUrls = *s - } - result["allowed_external_redirect_urls"] = allowedExternalRedirectUrls - - if input.DefaultProvider != "" { - result["default_provider"] = input.DefaultProvider - } - - if input.Issuer != nil { - result["issuer"] = *input.Issuer - } - - if input.RuntimeVersion != nil { - result["runtime_version"] = *input.RuntimeVersion - } - - if input.TokenRefreshExtensionHours != nil { - result["token_refresh_extension_hours"] = *input.TokenRefreshExtensionHours - } - - if input.TokenStoreEnabled != nil { - result["token_store_enabled"] = *input.TokenStoreEnabled - } - - if input.UnauthenticatedClientAction != "" { - result["unauthenticated_client_action"] = input.UnauthenticatedClientAction - } - - activeDirectorySettings := make([]interface{}, 0) - - if input.ClientID != nil { - activeDirectorySetting := make(map[string]interface{}) - - activeDirectorySetting["client_id"] = *input.ClientID - - if input.ClientSecret != nil { - activeDirectorySetting["client_secret"] = *input.ClientSecret - } - - if input.AllowedAudiences != nil { - activeDirectorySetting["allowed_audiences"] = *input.AllowedAudiences - } - - activeDirectorySettings = append(activeDirectorySettings, activeDirectorySetting) - } - - result["active_directory"] = activeDirectorySettings - - facebookSettings := make([]interface{}, 0) - - if input.FacebookAppID != nil { - facebookSetting := make(map[string]interface{}) - - facebookSetting["app_id"] = *input.FacebookAppID - - if input.FacebookAppSecret != nil { - facebookSetting["app_secret"] = *input.FacebookAppSecret - } - - if input.FacebookOAuthScopes != nil { - facebookSetting["oauth_scopes"] = *input.FacebookOAuthScopes - } - - facebookSettings = append(facebookSettings, facebookSetting) - } - - result["facebook"] = facebookSettings - - googleSettings := make([]interface{}, 0) - - if input.GoogleClientID != nil { - googleSetting := make(map[string]interface{}) - - googleSetting["client_id"] = *input.GoogleClientID - - if input.GoogleClientSecret != nil { - googleSetting["client_secret"] = *input.GoogleClientSecret - } - - if input.GoogleOAuthScopes != nil { - googleSetting["oauth_scopes"] = *input.GoogleOAuthScopes - } - - googleSettings = append(googleSettings, googleSetting) - } - - result["google"] = googleSettings - - microsoftSettings := make([]interface{}, 0) - - if input.MicrosoftAccountClientID != nil { - microsoftSetting := make(map[string]interface{}) - - microsoftSetting["client_id"] = *input.MicrosoftAccountClientID - - if input.MicrosoftAccountClientSecret != nil { - microsoftSetting["client_secret"] = *input.MicrosoftAccountClientSecret - } - - if input.MicrosoftAccountOAuthScopes != nil { - microsoftSetting["oauth_scopes"] = *input.MicrosoftAccountOAuthScopes - } - - microsoftSettings = append(microsoftSettings, microsoftSetting) - } - - result["microsoft"] = microsoftSettings - - twitterSettings := make([]interface{}, 0) - - if input.TwitterConsumerKey != nil { - twitterSetting := make(map[string]interface{}) - - twitterSetting["consumer_key"] = *input.TwitterConsumerKey - - if input.TwitterConsumerSecret != nil { - twitterSetting["consumer_secret"] = *input.TwitterConsumerSecret - } - - twitterSettings = append(twitterSettings, twitterSetting) - } - - result["twitter"] = twitterSettings - - return append(results, result) -} - -func FlattenAppServiceLogs(input *web.SiteLogsConfigProperties) []interface{} { - results := make([]interface{}, 0) - if input == nil { - return results - } - - result := make(map[string]interface{}) - - appLogs := make([]interface{}, 0) - if input.ApplicationLogs != nil { - appLogsItem := make(map[string]interface{}) - - if fileSystemInput := input.ApplicationLogs.FileSystem; fileSystemInput != nil { - appLogsItem["file_system_level"] = string(fileSystemInput.Level) - } - - blobStorage := make([]interface{}, 0) - if blobStorageInput := input.ApplicationLogs.AzureBlobStorage; blobStorageInput != nil { - blobStorageItem := make(map[string]interface{}) - - blobStorageItem["level"] = string(blobStorageInput.Level) - - if blobStorageInput.SasURL != nil { - blobStorageItem["sas_url"] = *blobStorageInput.SasURL - } - - if blobStorageInput.RetentionInDays != nil { - blobStorageItem["retention_in_days"] = *blobStorageInput.RetentionInDays - } - - // The API returns a non nil application logs object when other logs are specified so we'll check that this structure is empty before adding it to the statefile. - if blobStorageInput.SasURL != nil && *blobStorageInput.SasURL != "" { - blobStorage = append(blobStorage, blobStorageItem) - } - } - - appLogsItem["azure_blob_storage"] = blobStorage - appLogs = append(appLogs, appLogsItem) - } - result["application_logs"] = appLogs - - httpLogs := make([]interface{}, 0) - if input.HTTPLogs != nil { - httpLogsItem := make(map[string]interface{}) - - fileSystem := make([]interface{}, 0) - if fileSystemInput := input.HTTPLogs.FileSystem; fileSystemInput != nil { - fileSystemItem := make(map[string]interface{}) - - if fileSystemInput.RetentionInDays != nil { - fileSystemItem["retention_in_days"] = *fileSystemInput.RetentionInDays - } - - if fileSystemInput.RetentionInMb != nil { - fileSystemItem["retention_in_mb"] = *fileSystemInput.RetentionInMb - } - - // The API returns a non nil filesystem logs object when other logs are specified so we'll check that this is disabled before adding it to the statefile. - if fileSystemInput.Enabled != nil && *fileSystemInput.Enabled { - fileSystem = append(fileSystem, fileSystemItem) - } - } - - blobStorage := make([]interface{}, 0) - if blobStorageInput := input.HTTPLogs.AzureBlobStorage; blobStorageInput != nil { - blobStorageItem := make(map[string]interface{}) - - if blobStorageInput.SasURL != nil { - blobStorageItem["sas_url"] = *blobStorageInput.SasURL - } - - if blobStorageInput.RetentionInDays != nil { - blobStorageItem["retention_in_days"] = *blobStorageInput.RetentionInDays - } - - // The API returns a non nil blob logs object when other logs are specified so we'll check that this is disabled before adding it to the statefile. - if blobStorageInput.Enabled != nil && *blobStorageInput.Enabled { - blobStorage = append(blobStorage, blobStorageItem) - } - } - - httpLogsItem["file_system"] = fileSystem - httpLogsItem["azure_blob_storage"] = blobStorage - httpLogs = append(httpLogs, httpLogsItem) - } - result["http_logs"] = httpLogs - - return append(results, result) -} - -func ExpandAppServiceLogs(input interface{}) web.SiteLogsConfigProperties { - configs := input.([]interface{}) - logs := web.SiteLogsConfigProperties{} - - if len(configs) == 0 || configs[0] == nil { - return logs - } - - config := configs[0].(map[string]interface{}) - - if v, ok := config["application_logs"]; ok { - appLogsConfigs := v.([]interface{}) - - for _, config := range appLogsConfigs { - appLogsConfig := config.(map[string]interface{}) - - logs.ApplicationLogs = &web.ApplicationLogsConfig{} - - if v, ok := appLogsConfig["file_system_level"]; ok { - logs.ApplicationLogs.FileSystem = &web.FileSystemApplicationLogsConfig{ - Level: web.LogLevel(v.(string)), - } - } - - if v, ok := appLogsConfig["azure_blob_storage"]; ok { - storageConfigs := v.([]interface{}) - - for _, config := range storageConfigs { - storageConfig := config.(map[string]interface{}) - - logs.ApplicationLogs.AzureBlobStorage = &web.AzureBlobStorageApplicationLogsConfig{ - Level: web.LogLevel(storageConfig["level"].(string)), - SasURL: utils.String(storageConfig["sas_url"].(string)), - RetentionInDays: utils.Int32(int32(storageConfig["retention_in_days"].(int))), - } - } - } - } - } - - if v, ok := config["http_logs"]; ok { - httpLogsConfigs := v.([]interface{}) - - for _, config := range httpLogsConfigs { - httpLogsConfig := config.(map[string]interface{}) - - logs.HTTPLogs = &web.HTTPLogsConfig{} - - if v, ok := httpLogsConfig["file_system"]; ok { - fileSystemConfigs := v.([]interface{}) - - for _, config := range fileSystemConfigs { - fileSystemConfig := config.(map[string]interface{}) - - logs.HTTPLogs.FileSystem = &web.FileSystemHTTPLogsConfig{ - RetentionInMb: utils.Int32(int32(fileSystemConfig["retention_in_mb"].(int))), - RetentionInDays: utils.Int32(int32(fileSystemConfig["retention_in_days"].(int))), - Enabled: utils.Bool(true), - } - } - } - - if v, ok := httpLogsConfig["azure_blob_storage"]; ok { - storageConfigs := v.([]interface{}) - - for _, config := range storageConfigs { - storageConfig := config.(map[string]interface{}) - - logs.HTTPLogs.AzureBlobStorage = &web.AzureBlobStorageHTTPLogsConfig{ - SasURL: utils.String(storageConfig["sas_url"].(string)), - RetentionInDays: utils.Int32(int32(storageConfig["retention_in_days"].(int))), - Enabled: utils.Bool(true), - } - } - } - } - } - - return logs -} - -func ExpandAppServiceIdentity(input []interface{}) *web.ManagedServiceIdentity { - if len(input) == 0 { - return nil - } - identity := input[0].(map[string]interface{}) - identityType := web.ManagedServiceIdentityType(identity["type"].(string)) - - identityIds := make(map[string]*web.ManagedServiceIdentityUserAssignedIdentitiesValue) - for _, id := range identity["identity_ids"].([]interface{}) { - identityIds[id.(string)] = &web.ManagedServiceIdentityUserAssignedIdentitiesValue{} - } - - managedServiceIdentity := web.ManagedServiceIdentity{ - Type: identityType, - } - - if managedServiceIdentity.Type == web.ManagedServiceIdentityTypeUserAssigned || managedServiceIdentity.Type == SystemAssignedUserAssigned { - managedServiceIdentity.UserAssignedIdentities = identityIds - } - - return &managedServiceIdentity -} - -func FlattenAppServiceIdentity(identity *web.ManagedServiceIdentity) []interface{} { - if identity == nil { - return make([]interface{}, 0) - } - - principalId := "" - if identity.PrincipalID != nil { - principalId = *identity.PrincipalID - } - - tenantId := "" - if identity.TenantID != nil { - tenantId = *identity.TenantID - } - - identityIds := make([]string, 0) - if identity.UserAssignedIdentities != nil { - for key := range identity.UserAssignedIdentities { - identityIds = append(identityIds, key) - } - } - - return []interface{}{ - map[string]interface{}{ - "identity_ids": identityIds, - "principal_id": principalId, - "tenant_id": tenantId, - "type": string(identity.Type), - }, - } -} - -func ExpandAppServiceSiteConfig(input interface{}) (*web.SiteConfig, error) { - configs := input.([]interface{}) - siteConfig := &web.SiteConfig{} - - if len(configs) == 0 { - return siteConfig, nil - } - - config := configs[0].(map[string]interface{}) - - if v, ok := config["always_on"]; ok { - siteConfig.AlwaysOn = utils.Bool(v.(bool)) - } - - if v, ok := config["app_command_line"]; ok { - siteConfig.AppCommandLine = utils.String(v.(string)) - } - - if v, ok := config["default_documents"]; ok { - input := v.([]interface{}) - - documents := make([]string, 0) - for _, document := range input { - documents = append(documents, document.(string)) - } - - siteConfig.DefaultDocuments = &documents - } - - if v, ok := config["dotnet_framework_version"]; ok { - siteConfig.NetFrameworkVersion = utils.String(v.(string)) - } - - if v, ok := config["java_version"]; ok { - siteConfig.JavaVersion = utils.String(v.(string)) - } - - if v, ok := config["java_container"]; ok { - siteConfig.JavaContainer = utils.String(v.(string)) - } - - if v, ok := config["java_container_version"]; ok { - siteConfig.JavaContainerVersion = utils.String(v.(string)) - } - - if v, ok := config["linux_fx_version"]; ok { - siteConfig.LinuxFxVersion = utils.String(v.(string)) - } - - if v, ok := config["windows_fx_version"]; ok { - siteConfig.WindowsFxVersion = utils.String(v.(string)) - } - - if v, ok := config["http2_enabled"]; ok { - siteConfig.HTTP20Enabled = utils.Bool(v.(bool)) - } - - if v, ok := config["ip_restriction"]; ok { - ipSecurityRestrictions := v.([]interface{}) - restrictions := make([]web.IPSecurityRestriction, 0) - for i, ipSecurityRestriction := range ipSecurityRestrictions { - restriction := ipSecurityRestriction.(map[string]interface{}) - - ipAddress := restriction["ip_address"].(string) - vNetSubnetID := restriction["virtual_network_subnet_id"].(string) - name := restriction["name"].(string) - priority := restriction["priority"].(int) - action := restriction["action"].(string) - if vNetSubnetID != "" && ipAddress != "" { - return siteConfig, fmt.Errorf(fmt.Sprintf("only one of `ip_address` or `virtual_network_subnet_id` can be set for `site_config.0.ip_restriction.%d`", i)) - } - - if vNetSubnetID == "" && ipAddress == "" { - return siteConfig, fmt.Errorf(fmt.Sprintf("one of `ip_address` or `virtual_network_subnet_id` must be set for `site_config.0.ip_restriction.%d`", i)) - } - - ipSecurityRestriction := web.IPSecurityRestriction{} - if ipAddress == "Any" { - continue - } - - if ipAddress != "" { - ipSecurityRestriction.IPAddress = &ipAddress - } - - if vNetSubnetID != "" { - ipSecurityRestriction.VnetSubnetResourceID = &vNetSubnetID - } - - if name != "" { - ipSecurityRestriction.Name = &name - } - - if priority != 0 { - ipSecurityRestriction.Priority = utils.Int32(int32(priority)) - } - - if action != "" { - ipSecurityRestriction.Action = &action - } - - restrictions = append(restrictions, ipSecurityRestriction) - } - siteConfig.IPSecurityRestrictions = &restrictions - } - - if v, ok := config["scm_use_main_ip_restriction"]; ok { - siteConfig.ScmIPSecurityRestrictionsUseMain = utils.Bool(v.(bool)) - } - - if v, ok := config["scm_ip_restriction"]; ok { - scmIPSecurityRestrictions := v.([]interface{}) - scmRestrictions := make([]web.IPSecurityRestriction, 0) - for i, scmIPSecurityRestriction := range scmIPSecurityRestrictions { - scmRestriction := scmIPSecurityRestriction.(map[string]interface{}) - - ipAddress := scmRestriction["ip_address"].(string) - vNetSubnetID := scmRestriction["virtual_network_subnet_id"].(string) - name := scmRestriction["name"].(string) - priority := scmRestriction["priority"].(int) - action := scmRestriction["action"].(string) - if vNetSubnetID != "" && ipAddress != "" { - return siteConfig, fmt.Errorf(fmt.Sprintf("only one of `ip_address` or `virtual_network_subnet_id` can be set for `site_config.0.scm_ip_restriction.%d`", i)) - } - - if vNetSubnetID == "" && ipAddress == "" { - return siteConfig, fmt.Errorf(fmt.Sprintf("one of `ip_address` or `virtual_network_subnet_id` must be set for `site_config.0.scm_ip_restriction.%d`", i)) - } - - scmIPSecurityRestriction := web.IPSecurityRestriction{} - if ipAddress == "Any" { - continue - } - - if ipAddress != "" { - scmIPSecurityRestriction.IPAddress = &ipAddress - } - - if vNetSubnetID != "" { - scmIPSecurityRestriction.VnetSubnetResourceID = &vNetSubnetID - } - - if name != "" { - scmIPSecurityRestriction.Name = &name - } - - if priority != 0 { - scmIPSecurityRestriction.Priority = utils.Int32(int32(priority)) - } - - if action != "" { - scmIPSecurityRestriction.Action = &action - } - - scmRestrictions = append(scmRestrictions, scmIPSecurityRestriction) - } - siteConfig.ScmIPSecurityRestrictions = &scmRestrictions - } - - if v, ok := config["local_mysql_enabled"]; ok { - siteConfig.LocalMySQLEnabled = utils.Bool(v.(bool)) - } - - if v, ok := config["managed_pipeline_mode"]; ok { - siteConfig.ManagedPipelineMode = web.ManagedPipelineMode(v.(string)) - } - - if v, ok := config["php_version"]; ok { - siteConfig.PhpVersion = utils.String(v.(string)) - } - - if v, ok := config["python_version"]; ok { - siteConfig.PythonVersion = utils.String(v.(string)) - } - - if v, ok := config["remote_debugging_enabled"]; ok { - siteConfig.RemoteDebuggingEnabled = utils.Bool(v.(bool)) - } - - if v, ok := config["remote_debugging_version"]; ok { - siteConfig.RemoteDebuggingVersion = utils.String(v.(string)) - } - - if v, ok := config["use_32_bit_worker_process"]; ok { - siteConfig.Use32BitWorkerProcess = utils.Bool(v.(bool)) - } - - if v, ok := config["websockets_enabled"]; ok { - siteConfig.WebSocketsEnabled = utils.Bool(v.(bool)) - } - - if v, ok := config["scm_type"]; ok { - siteConfig.ScmType = web.ScmType(v.(string)) - } - - if v, ok := config["ftps_state"]; ok { - siteConfig.FtpsState = web.FtpsState(v.(string)) - } - - if v, ok := config["health_check_path"]; ok { - siteConfig.HealthCheckPath = utils.String(v.(string)) - } - - if v, ok := config["min_tls_version"]; ok { - siteConfig.MinTLSVersion = web.SupportedTLSVersions(v.(string)) - } - - if v, ok := config["cors"]; ok { - corsSettings := v.(interface{}) - expand := ExpandWebCorsSettings(corsSettings) - siteConfig.Cors = &expand - } - - if v, ok := config["auto_swap_slot_name"]; ok { - siteConfig.AutoSwapSlotName = utils.String(v.(string)) - } - - return siteConfig, nil -} - -func FlattenAppServiceSiteConfig(input *web.SiteConfig) []interface{} { - results := make([]interface{}, 0) - result := make(map[string]interface{}) - - if input == nil { - log.Printf("[DEBUG] SiteConfig is nil") - return results - } - - if input.AlwaysOn != nil { - result["always_on"] = *input.AlwaysOn - } - - if input.AppCommandLine != nil { - result["app_command_line"] = *input.AppCommandLine - } - - documents := make([]string, 0) - if s := input.DefaultDocuments; s != nil { - documents = *s - } - result["default_documents"] = documents - - if input.NetFrameworkVersion != nil { - result["dotnet_framework_version"] = *input.NetFrameworkVersion - } - - if input.JavaVersion != nil { - result["java_version"] = *input.JavaVersion - } - - if input.JavaContainer != nil { - result["java_container"] = *input.JavaContainer - } - - if input.JavaContainerVersion != nil { - result["java_container_version"] = *input.JavaContainerVersion - } - - if input.LocalMySQLEnabled != nil { - result["local_mysql_enabled"] = *input.LocalMySQLEnabled - } - - if input.HTTP20Enabled != nil { - result["http2_enabled"] = *input.HTTP20Enabled - } - - restrictions := make([]interface{}, 0) - if vs := input.IPSecurityRestrictions; vs != nil { - for _, v := range *vs { - block := make(map[string]interface{}) - - if ip := v.IPAddress; ip != nil { - if *ip == "Any" { - continue - } else { - block["ip_address"] = *ip - } - } - if vNetSubnetID := v.VnetSubnetResourceID; vNetSubnetID != nil { - block["virtual_network_subnet_id"] = *vNetSubnetID - } - if name := v.Name; name != nil { - block["name"] = *name - } - if priority := v.Priority; priority != nil { - block["priority"] = *priority - } - - if action := v.Action; action != nil { - block["action"] = *action - } - - restrictions = append(restrictions, block) - } - } - result["ip_restriction"] = restrictions - - if input.ScmIPSecurityRestrictionsUseMain != nil { - result["scm_use_main_ip_restriction"] = *input.ScmIPSecurityRestrictionsUseMain - } - - scmRestrictions := make([]interface{}, 0) - if vs := input.ScmIPSecurityRestrictions; vs != nil { - for _, v := range *vs { - block := make(map[string]interface{}) - - if ip := v.IPAddress; ip != nil { - if *ip == "Any" { - continue - } else { - block["ip_address"] = *ip - } - } - if vNetSubnetID := v.VnetSubnetResourceID; vNetSubnetID != nil { - block["virtual_network_subnet_id"] = *vNetSubnetID - } - if name := v.Name; name != nil { - block["name"] = *name - } - if priority := v.Priority; priority != nil { - block["priority"] = *priority - } - - if action := v.Action; action != nil { - block["action"] = *action - } - scmRestrictions = append(scmRestrictions, block) - } - } - result["scm_ip_restriction"] = scmRestrictions - - result["managed_pipeline_mode"] = string(input.ManagedPipelineMode) - - if input.PhpVersion != nil { - result["php_version"] = *input.PhpVersion - } - - if input.PythonVersion != nil { - result["python_version"] = *input.PythonVersion - } - - if input.RemoteDebuggingEnabled != nil { - result["remote_debugging_enabled"] = *input.RemoteDebuggingEnabled - } - - if input.RemoteDebuggingVersion != nil { - result["remote_debugging_version"] = *input.RemoteDebuggingVersion - } - - if input.Use32BitWorkerProcess != nil { - result["use_32_bit_worker_process"] = *input.Use32BitWorkerProcess - } - - if input.WebSocketsEnabled != nil { - result["websockets_enabled"] = *input.WebSocketsEnabled - } - - if input.LinuxFxVersion != nil { - result["linux_fx_version"] = *input.LinuxFxVersion - } - - if input.WindowsFxVersion != nil { - result["windows_fx_version"] = *input.WindowsFxVersion - } - - result["scm_type"] = string(input.ScmType) - result["ftps_state"] = string(input.FtpsState) - - if input.HealthCheckPath != nil { - result["health_check_path"] = *input.HealthCheckPath - } - - result["min_tls_version"] = string(input.MinTLSVersion) - - result["cors"] = FlattenWebCorsSettings(input.Cors) - - if input.AutoSwapSlotName != nil { - result["auto_swap_slot_name"] = *input.AutoSwapSlotName - } - - return append(results, result) -} - -func ExpandAppServiceStorageAccounts(input []interface{}) map[string]*web.AzureStorageInfoValue { - output := make(map[string]*web.AzureStorageInfoValue, len(input)) - - for _, v := range input { - vals := v.(map[string]interface{}) - - saName := vals["name"].(string) - saType := vals["type"].(string) - saAccountName := vals["account_name"].(string) - saShareName := vals["share_name"].(string) - saAccessKey := vals["access_key"].(string) - saMountPath := vals["mount_path"].(string) - - output[saName] = &web.AzureStorageInfoValue{ - Type: web.AzureStorageType(saType), - AccountName: utils.String(saAccountName), - ShareName: utils.String(saShareName), - AccessKey: utils.String(saAccessKey), - MountPath: utils.String(saMountPath), - } - } - - return output -} - -func FlattenAppServiceStorageAccounts(input map[string]*web.AzureStorageInfoValue) []interface{} { - results := make([]interface{}, 0) - - for k, v := range input { - result := make(map[string]interface{}) - result["name"] = k - result["type"] = string(v.Type) - if v.AccountName != nil { - result["account_name"] = *v.AccountName - } - if v.ShareName != nil { - result["share_name"] = *v.ShareName - } - if v.AccessKey != nil { - result["access_key"] = *v.AccessKey - } - if v.MountPath != nil { - result["mount_path"] = *v.MountPath - } - results = append(results, result) - } - - return results -} diff --git a/azurerm/internal/services/web/app_service.go b/azurerm/internal/services/web/app_service.go index ff631db710f8..15ac1f16f9e9 100644 --- a/azurerm/internal/services/web/app_service.go +++ b/azurerm/internal/services/web/app_service.go @@ -2,48 +2,1877 @@ package web import ( "fmt" + "log" + "regexp" + "strings" + "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2019-08-01/web" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) -type AppServiceResourceID struct { - ResourceGroup string - Name string +const ( + // TODO: switch back once https://github.com/Azure/azure-rest-api-specs/pull/8435 has been fixed + SystemAssignedUserAssigned web.ManagedServiceIdentityType = "SystemAssigned, UserAssigned" +) + +func schemaAppServiceAadAuthSettings() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "allowed_audiences": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + } +} + +func schemaAppServiceFacebookAuthSettings() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "app_id": { + Type: schema.TypeString, + Required: true, + }, + "app_secret": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + }, + "oauth_scopes": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + } +} + +func schemaAppServiceGoogleAuthSettings() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + }, + "oauth_scopes": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + } +} + +func schemaAppServiceMicrosoftAuthSettings() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + }, + "oauth_scopes": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + } +} + +func schemaAppServiceTwitterAuthSettings() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "consumer_key": { + Type: schema.TypeString, + Required: true, + }, + "consumer_secret": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + }, + }, + }, + } +} + +func schemaAppServiceAuthSettings() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enabled": { + Type: schema.TypeBool, + Required: true, + }, + + "additional_login_params": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + + "allowed_external_redirect_urls": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "default_provider": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.AzureActiveDirectory), + string(web.Facebook), + string(web.Google), + string(web.MicrosoftAccount), + string(web.Twitter), + }, false), + }, + + "issuer": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.IsURLWithScheme([]string{"http", "https"}), + }, + + "runtime_version": { + Type: schema.TypeString, + Optional: true, + }, + + "token_refresh_extension_hours": { + Type: schema.TypeFloat, + Optional: true, + Default: 72, + }, + + "token_store_enabled": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "unauthenticated_client_action": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.AllowAnonymous), + string(web.RedirectToLoginPage), + }, false), + }, + + "active_directory": schemaAppServiceAadAuthSettings(), + + "facebook": schemaAppServiceFacebookAuthSettings(), + + "google": schemaAppServiceGoogleAuthSettings(), + + "microsoft": schemaAppServiceMicrosoftAuthSettings(), + + "twitter": schemaAppServiceTwitterAuthSettings(), + }, + }, + } +} + +func schemaAppServiceIdentity() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "identity_ids": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.NoZeroValues, + }, + }, + + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.ManagedServiceIdentityTypeNone), + string(web.ManagedServiceIdentityTypeSystemAssigned), + string(SystemAssignedUserAssigned), + string(web.ManagedServiceIdentityTypeUserAssigned), + }, true), + DiffSuppressFunc: suppress.CaseDifference, + }, + + "principal_id": { + Type: schema.TypeString, + Computed: true, + }, + + "tenant_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + } +} + +func schemaAppServiceSiteConfig() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "always_on": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "app_command_line": { + Type: schema.TypeString, + Optional: true, + }, + + "default_documents": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "dotnet_framework_version": { + Type: schema.TypeString, + Optional: true, + Default: "v4.0", + ValidateFunc: validation.StringInSlice([]string{ + "v2.0", + "v4.0", + }, true), + DiffSuppressFunc: suppress.CaseDifference, + }, + + "http2_enabled": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "ip_restriction": schemaAppServiceIpRestriction(), + + "scm_use_main_ip_restriction": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "scm_ip_restriction": schemaAppServiceIpRestriction(), + + "java_version": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringMatch( + regexp.MustCompile(`^(1\.7|1\.8|11)`), + `Invalid Java version provided`), + }, + + "java_container": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "JAVA", + "JETTY", + "TOMCAT", + }, true), + DiffSuppressFunc: suppress.CaseDifference, + }, + + "java_container_version": { + Type: schema.TypeString, + Optional: true, + }, + + "local_mysql_enabled": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + + "managed_pipeline_mode": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.Classic), + string(web.Integrated), + }, true), + DiffSuppressFunc: suppress.CaseDifference, + }, + + "php_version": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "5.5", + "5.6", + "7.0", + "7.1", + "7.2", + "7.3", + }, false), + }, + + "python_version": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "2.7", + "3.4", + }, false), + }, + + "remote_debugging_enabled": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "remote_debugging_version": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + "VS2012", // TODO for 3.0 - remove VS2012, VS2013, VS2015 + "VS2013", + "VS2015", + "VS2017", + "VS2019", + }, true), + DiffSuppressFunc: suppress.CaseDifference, + }, + + "scm_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.ScmTypeBitbucketGit), + string(web.ScmTypeBitbucketHg), + string(web.ScmTypeCodePlexGit), + string(web.ScmTypeCodePlexHg), + string(web.ScmTypeDropbox), + string(web.ScmTypeExternalGit), + string(web.ScmTypeExternalHg), + string(web.ScmTypeGitHub), + string(web.ScmTypeLocalGit), + string(web.ScmTypeNone), + string(web.ScmTypeOneDrive), + string(web.ScmTypeTfs), + string(web.ScmTypeVSO), + // Not in the specs, but is set by Azure Pipelines + // https://github.com/Microsoft/azure-pipelines-tasks/blob/master/Tasks/AzureRmWebAppDeploymentV4/operations/AzureAppServiceUtility.ts#L19 + // upstream issue: https://github.com/Azure/azure-rest-api-specs/issues/5345 + "VSTSRM", + }, false), + }, + + "use_32_bit_worker_process": { + Type: schema.TypeBool, + Optional: true, + }, + + "websockets_enabled": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + + "ftps_state": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.AllAllowed), + string(web.Disabled), + string(web.FtpsOnly), + }, false), + }, + + "health_check_path": { + Type: schema.TypeString, + Optional: true, + }, + + "linux_fx_version": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "windows_fx_version": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "min_tls_version": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.OneFullStopZero), + string(web.OneFullStopOne), + string(web.OneFullStopTwo), + }, false), + }, + + "cors": azure.SchemaWebCorsSettings(), + + "auto_swap_slot_name": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + } } -func ParseAppServiceID(input string) (*AppServiceResourceID, error) { - id, err := azure.ParseAzureResourceID(input) - if err != nil { - return nil, fmt.Errorf("[ERROR] Unable to parse App Service ID %q: %+v", input, err) +func schemaAppServiceLogsConfig() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "application_logs": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "file_system_level": { + Type: schema.TypeString, + Optional: true, + Default: "Off", + ConflictsWith: []string{"logs.0.http_logs.0.azure_blob_storage"}, + ValidateFunc: validation.StringInSlice([]string{ + string(web.Error), + string(web.Information), + string(web.Off), + string(web.Verbose), + string(web.Warning), + }, false), + }, + "azure_blob_storage": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "level": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.Error), + string(web.Information), + string(web.Off), + string(web.Verbose), + string(web.Warning), + }, false), + }, + "sas_url": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + }, + "retention_in_days": { + Type: schema.TypeInt, + Required: true, + }, + }, + }, + }, + }, + }, + }, + "http_logs": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "file_system": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "retention_in_mb": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(25, 100), + }, + "retention_in_days": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(0), + }, + }, + }, + ConflictsWith: []string{"logs.0.http_logs.0.azure_blob_storage"}, + }, + "azure_blob_storage": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "sas_url": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + }, + "retention_in_days": { + Type: schema.TypeInt, + Required: true, + }, + }, + }, + ConflictsWith: []string{"logs.0.http_logs.0.file_system"}, + }, + }, + }, + }, + }, + }, } +} + +func schemaAppServiceStorageAccounts() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.AzureBlob), + string(web.AzureFiles), + }, false), + }, + + "account_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, - appService := AppServiceResourceID{ - ResourceGroup: id.ResourceGroup, + "share_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "access_key": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "mount_path": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, } +} + +func schemaAppServiceDataSourceSiteConfig() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "always_on": { + Type: schema.TypeBool, + Computed: true, + }, + + "app_command_line": { + Type: schema.TypeString, + Computed: true, + }, + + "default_documents": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "dotnet_framework_version": { + Type: schema.TypeString, + Computed: true, + }, + + "http2_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + + "ip_restriction": schemaAppServiceDataSourceIpRestriction(), + + "scm_use_main_ip_restriction": { + Type: schema.TypeBool, + Computed: true, + }, + + "scm_ip_restriction": schemaAppServiceDataSourceIpRestriction(), + + "java_version": { + Type: schema.TypeString, + Computed: true, + }, + + "java_container": { + Type: schema.TypeString, + Computed: true, + }, + + "java_container_version": { + Type: schema.TypeString, + Computed: true, + }, + + "local_mysql_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + + "managed_pipeline_mode": { + Type: schema.TypeString, + Computed: true, + }, + + "php_version": { + Type: schema.TypeString, + Computed: true, + }, - if appService.Name, err = id.PopSegment("sites"); err != nil { - return nil, err + "python_version": { + Type: schema.TypeString, + Computed: true, + }, + + "remote_debugging_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + + "remote_debugging_version": { + Type: schema.TypeString, + Computed: true, + }, + + "scm_type": { + Type: schema.TypeString, + Computed: true, + }, + + "use_32_bit_worker_process": { + Type: schema.TypeBool, + Computed: true, + }, + + "websockets_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + + "ftps_state": { + Type: schema.TypeString, + Computed: true, + }, + + "health_check_path": { + Type: schema.TypeString, + Computed: true, + }, + + "linux_fx_version": { + Type: schema.TypeString, + Computed: true, + }, + + "windows_fx_version": { + Type: schema.TypeString, + Computed: true, + }, + + "min_tls_version": { + Type: schema.TypeString, + Computed: true, + }, + + "cors": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "allowed_origins": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "support_credentials": { + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + }, + }, + }, } +} + +func schemaAppServiceIpRestriction() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Computed: true, + ConfigMode: schema.SchemaConfigModeAttr, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip_address": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validate.CIDR, + }, + + "subnet_id": { + // TODO - Remove in 3.0 + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringIsNotEmpty, + Deprecated: "This field has been deprecated in favour of `virtual_network_subnet_id` and will be removed in a future version of the provider", + }, + + "virtual_network_subnet_id": { + Type: schema.TypeString, + Computed: true, // TODO Remove `Computed` in 3.0 + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, - if err := id.ValidateNoEmptySegments(input); err != nil { - return nil, err + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "priority": { + Type: schema.TypeInt, + Optional: true, + Default: 65000, + ValidateFunc: validation.IntBetween(1, 2147483647), + }, + + "action": { + Type: schema.TypeString, + Default: "Allow", + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "Allow", + "Deny", + }, false), + }, + }, + }, } +} - return &appService, nil +func schemaAppServiceDataSourceIpRestriction() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip_address": { + Type: schema.TypeString, + Computed: true, + }, + "virtual_network_subnet_id": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "priority": { + Type: schema.TypeInt, + Computed: true, + }, + "action": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + } } -// ValidateAppServiceID validates that the specified ID is a valid App Service ID -func ValidateAppServiceID(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 +func expandAppServiceAuthSettings(input []interface{}) web.SiteAuthSettingsProperties { + siteAuthSettingsProperties := web.SiteAuthSettingsProperties{} + + if len(input) == 0 { + return siteAuthSettingsProperties + } + + setting := input[0].(map[string]interface{}) + + if v, ok := setting["enabled"]; ok { + siteAuthSettingsProperties.Enabled = utils.Bool(v.(bool)) } - if _, err := ParseAppServiceID(v); err != nil { - errors = append(errors, fmt.Errorf("Can not parse %q as a resource id: %v", k, err)) - return + if v, ok := setting["additional_login_params"]; ok { + input := v.(map[string]interface{}) + + additionalLoginParams := make([]string, 0) + for k, v := range input { + additionalLoginParams = append(additionalLoginParams, fmt.Sprintf("%s=%s", k, v.(string))) + } + + siteAuthSettingsProperties.AdditionalLoginParams = &additionalLoginParams + } + + if v, ok := setting["allowed_external_redirect_urls"]; ok { + input := v.([]interface{}) + + allowedExternalRedirectUrls := make([]string, 0) + for _, param := range input { + allowedExternalRedirectUrls = append(allowedExternalRedirectUrls, param.(string)) + } + + siteAuthSettingsProperties.AllowedExternalRedirectUrls = &allowedExternalRedirectUrls + } + + if v, ok := setting["default_provider"]; ok { + siteAuthSettingsProperties.DefaultProvider = web.BuiltInAuthenticationProvider(v.(string)) + } + + if v, ok := setting["issuer"]; ok { + siteAuthSettingsProperties.Issuer = utils.String(v.(string)) + } + + if v, ok := setting["runtime_version"]; ok { + siteAuthSettingsProperties.RuntimeVersion = utils.String(v.(string)) + } + + if v, ok := setting["token_refresh_extension_hours"]; ok { + siteAuthSettingsProperties.TokenRefreshExtensionHours = utils.Float(v.(float64)) + } + + if v, ok := setting["token_store_enabled"]; ok { + siteAuthSettingsProperties.TokenStoreEnabled = utils.Bool(v.(bool)) + } + + if v, ok := setting["unauthenticated_client_action"]; ok { + siteAuthSettingsProperties.UnauthenticatedClientAction = web.UnauthenticatedClientAction(v.(string)) + } + + if v, ok := setting["active_directory"]; ok { + activeDirectorySettings := v.([]interface{}) + + for _, setting := range activeDirectorySettings { + if setting == nil { + continue + } + + activeDirectorySetting := setting.(map[string]interface{}) + + if v, ok := activeDirectorySetting["client_id"]; ok { + siteAuthSettingsProperties.ClientID = utils.String(v.(string)) + } + + if v, ok := activeDirectorySetting["client_secret"]; ok { + siteAuthSettingsProperties.ClientSecret = utils.String(v.(string)) + } + + if v, ok := activeDirectorySetting["allowed_audiences"]; ok { + input := v.([]interface{}) + + allowedAudiences := make([]string, 0) + for _, param := range input { + allowedAudiences = append(allowedAudiences, param.(string)) + } + + siteAuthSettingsProperties.AllowedAudiences = &allowedAudiences + } + } + } + + if v, ok := setting["facebook"]; ok { + facebookSettings := v.([]interface{}) + + for _, setting := range facebookSettings { + facebookSetting := setting.(map[string]interface{}) + + if v, ok := facebookSetting["app_id"]; ok { + siteAuthSettingsProperties.FacebookAppID = utils.String(v.(string)) + } + + if v, ok := facebookSetting["app_secret"]; ok { + siteAuthSettingsProperties.FacebookAppSecret = utils.String(v.(string)) + } + + if v, ok := facebookSetting["oauth_scopes"]; ok { + input := v.([]interface{}) + + oauthScopes := make([]string, 0) + for _, param := range input { + oauthScopes = append(oauthScopes, param.(string)) + } + + siteAuthSettingsProperties.FacebookOAuthScopes = &oauthScopes + } + } + } + + if v, ok := setting["google"]; ok { + googleSettings := v.([]interface{}) + + for _, setting := range googleSettings { + googleSetting := setting.(map[string]interface{}) + + if v, ok := googleSetting["client_id"]; ok { + siteAuthSettingsProperties.GoogleClientID = utils.String(v.(string)) + } + + if v, ok := googleSetting["client_secret"]; ok { + siteAuthSettingsProperties.GoogleClientSecret = utils.String(v.(string)) + } + + if v, ok := googleSetting["oauth_scopes"]; ok { + input := v.([]interface{}) + + oauthScopes := make([]string, 0) + for _, param := range input { + oauthScopes = append(oauthScopes, param.(string)) + } + + siteAuthSettingsProperties.GoogleOAuthScopes = &oauthScopes + } + } + } + + if v, ok := setting["microsoft"]; ok { + microsoftSettings := v.([]interface{}) + + for _, setting := range microsoftSettings { + microsoftSetting := setting.(map[string]interface{}) + + if v, ok := microsoftSetting["client_id"]; ok { + siteAuthSettingsProperties.MicrosoftAccountClientID = utils.String(v.(string)) + } + + if v, ok := microsoftSetting["client_secret"]; ok { + siteAuthSettingsProperties.MicrosoftAccountClientSecret = utils.String(v.(string)) + } + + if v, ok := microsoftSetting["oauth_scopes"]; ok { + input := v.([]interface{}) + + oauthScopes := make([]string, 0) + for _, param := range input { + oauthScopes = append(oauthScopes, param.(string)) + } + + siteAuthSettingsProperties.MicrosoftAccountOAuthScopes = &oauthScopes + } + } + } + + if v, ok := setting["twitter"]; ok { + twitterSettings := v.([]interface{}) + + for _, setting := range twitterSettings { + twitterSetting := setting.(map[string]interface{}) + + if v, ok := twitterSetting["consumer_key"]; ok { + siteAuthSettingsProperties.TwitterConsumerKey = utils.String(v.(string)) + } + + if v, ok := twitterSetting["consumer_secret"]; ok { + siteAuthSettingsProperties.TwitterConsumerSecret = utils.String(v.(string)) + } + } + } + + return siteAuthSettingsProperties +} + +func flattenAdditionalLoginParams(input *[]string) map[string]interface{} { + result := make(map[string]interface{}) + + if input == nil { + return result + } + + for _, k := range *input { + parts := strings.Split(k, "=") + if len(parts) != 2 { + continue // Params not following the format `key=value` is considered malformed and will be ignored. + } + key := parts[0] + value := parts[1] + + result[key] = value + } + + return result +} + +func flattenAppServiceAuthSettings(input *web.SiteAuthSettingsProperties) []interface{} { + results := make([]interface{}, 0) + if input == nil { + return results + } + + result := make(map[string]interface{}) + + if input.Enabled != nil { + result["enabled"] = *input.Enabled + } + + result["additional_login_params"] = flattenAdditionalLoginParams(input.AdditionalLoginParams) + + allowedExternalRedirectUrls := make([]string, 0) + if s := input.AllowedExternalRedirectUrls; s != nil { + allowedExternalRedirectUrls = *s + } + result["allowed_external_redirect_urls"] = allowedExternalRedirectUrls + + if input.DefaultProvider != "" { + result["default_provider"] = input.DefaultProvider + } + + if input.Issuer != nil { + result["issuer"] = *input.Issuer + } + + if input.RuntimeVersion != nil { + result["runtime_version"] = *input.RuntimeVersion + } + + if input.TokenRefreshExtensionHours != nil { + result["token_refresh_extension_hours"] = *input.TokenRefreshExtensionHours + } + + if input.TokenStoreEnabled != nil { + result["token_store_enabled"] = *input.TokenStoreEnabled + } + + if input.UnauthenticatedClientAction != "" { + result["unauthenticated_client_action"] = input.UnauthenticatedClientAction + } + + activeDirectorySettings := make([]interface{}, 0) + + if input.ClientID != nil { + activeDirectorySetting := make(map[string]interface{}) + + activeDirectorySetting["client_id"] = *input.ClientID + + if input.ClientSecret != nil { + activeDirectorySetting["client_secret"] = *input.ClientSecret + } + + if input.AllowedAudiences != nil { + activeDirectorySetting["allowed_audiences"] = *input.AllowedAudiences + } + + activeDirectorySettings = append(activeDirectorySettings, activeDirectorySetting) + } + + result["active_directory"] = activeDirectorySettings + + facebookSettings := make([]interface{}, 0) + + if input.FacebookAppID != nil { + facebookSetting := make(map[string]interface{}) + + facebookSetting["app_id"] = *input.FacebookAppID + + if input.FacebookAppSecret != nil { + facebookSetting["app_secret"] = *input.FacebookAppSecret + } + + if input.FacebookOAuthScopes != nil { + facebookSetting["oauth_scopes"] = *input.FacebookOAuthScopes + } + + facebookSettings = append(facebookSettings, facebookSetting) + } + + result["facebook"] = facebookSettings + + googleSettings := make([]interface{}, 0) + + if input.GoogleClientID != nil { + googleSetting := make(map[string]interface{}) + + googleSetting["client_id"] = *input.GoogleClientID + + if input.GoogleClientSecret != nil { + googleSetting["client_secret"] = *input.GoogleClientSecret + } + + if input.GoogleOAuthScopes != nil { + googleSetting["oauth_scopes"] = *input.GoogleOAuthScopes + } + + googleSettings = append(googleSettings, googleSetting) + } + + result["google"] = googleSettings + + microsoftSettings := make([]interface{}, 0) + + if input.MicrosoftAccountClientID != nil { + microsoftSetting := make(map[string]interface{}) + + microsoftSetting["client_id"] = *input.MicrosoftAccountClientID + + if input.MicrosoftAccountClientSecret != nil { + microsoftSetting["client_secret"] = *input.MicrosoftAccountClientSecret + } + + if input.MicrosoftAccountOAuthScopes != nil { + microsoftSetting["oauth_scopes"] = *input.MicrosoftAccountOAuthScopes + } + + microsoftSettings = append(microsoftSettings, microsoftSetting) + } + + result["microsoft"] = microsoftSettings + + twitterSettings := make([]interface{}, 0) + + if input.TwitterConsumerKey != nil { + twitterSetting := make(map[string]interface{}) + + twitterSetting["consumer_key"] = *input.TwitterConsumerKey + + if input.TwitterConsumerSecret != nil { + twitterSetting["consumer_secret"] = *input.TwitterConsumerSecret + } + + twitterSettings = append(twitterSettings, twitterSetting) + } + + result["twitter"] = twitterSettings + + return append(results, result) +} + +func flattenAppServiceLogs(input *web.SiteLogsConfigProperties) []interface{} { + results := make([]interface{}, 0) + if input == nil { + return results + } + + result := make(map[string]interface{}) + + appLogs := make([]interface{}, 0) + if input.ApplicationLogs != nil { + appLogsItem := make(map[string]interface{}) + + if fileSystemInput := input.ApplicationLogs.FileSystem; fileSystemInput != nil { + appLogsItem["file_system_level"] = string(fileSystemInput.Level) + } + + blobStorage := make([]interface{}, 0) + if blobStorageInput := input.ApplicationLogs.AzureBlobStorage; blobStorageInput != nil { + blobStorageItem := make(map[string]interface{}) + + blobStorageItem["level"] = string(blobStorageInput.Level) + + if blobStorageInput.SasURL != nil { + blobStorageItem["sas_url"] = *blobStorageInput.SasURL + } + + if blobStorageInput.RetentionInDays != nil { + blobStorageItem["retention_in_days"] = *blobStorageInput.RetentionInDays + } + + // The API returns a non nil application logs object when other logs are specified so we'll check that this structure is empty before adding it to the statefile. + if blobStorageInput.SasURL != nil && *blobStorageInput.SasURL != "" { + blobStorage = append(blobStorage, blobStorageItem) + } + } + + appLogsItem["azure_blob_storage"] = blobStorage + appLogs = append(appLogs, appLogsItem) + } + result["application_logs"] = appLogs + + httpLogs := make([]interface{}, 0) + if input.HTTPLogs != nil { + httpLogsItem := make(map[string]interface{}) + + fileSystem := make([]interface{}, 0) + if fileSystemInput := input.HTTPLogs.FileSystem; fileSystemInput != nil { + fileSystemItem := make(map[string]interface{}) + + if fileSystemInput.RetentionInDays != nil { + fileSystemItem["retention_in_days"] = *fileSystemInput.RetentionInDays + } + + if fileSystemInput.RetentionInMb != nil { + fileSystemItem["retention_in_mb"] = *fileSystemInput.RetentionInMb + } + + // The API returns a non nil filesystem logs object when other logs are specified so we'll check that this is disabled before adding it to the statefile. + if fileSystemInput.Enabled != nil && *fileSystemInput.Enabled { + fileSystem = append(fileSystem, fileSystemItem) + } + } + + blobStorage := make([]interface{}, 0) + if blobStorageInput := input.HTTPLogs.AzureBlobStorage; blobStorageInput != nil { + blobStorageItem := make(map[string]interface{}) + + if blobStorageInput.SasURL != nil { + blobStorageItem["sas_url"] = *blobStorageInput.SasURL + } + + if blobStorageInput.RetentionInDays != nil { + blobStorageItem["retention_in_days"] = *blobStorageInput.RetentionInDays + } + + // The API returns a non nil blob logs object when other logs are specified so we'll check that this is disabled before adding it to the statefile. + if blobStorageInput.Enabled != nil && *blobStorageInput.Enabled { + blobStorage = append(blobStorage, blobStorageItem) + } + } + + httpLogsItem["file_system"] = fileSystem + httpLogsItem["azure_blob_storage"] = blobStorage + httpLogs = append(httpLogs, httpLogsItem) + } + result["http_logs"] = httpLogs + + return append(results, result) +} + +func expandAppServiceLogs(input interface{}) web.SiteLogsConfigProperties { + configs := input.([]interface{}) + logs := web.SiteLogsConfigProperties{} + + if len(configs) == 0 || configs[0] == nil { + return logs + } + + config := configs[0].(map[string]interface{}) + + if v, ok := config["application_logs"]; ok { + appLogsConfigs := v.([]interface{}) + + for _, config := range appLogsConfigs { + appLogsConfig := config.(map[string]interface{}) + + logs.ApplicationLogs = &web.ApplicationLogsConfig{} + + if v, ok := appLogsConfig["file_system_level"]; ok { + logs.ApplicationLogs.FileSystem = &web.FileSystemApplicationLogsConfig{ + Level: web.LogLevel(v.(string)), + } + } + + if v, ok := appLogsConfig["azure_blob_storage"]; ok { + storageConfigs := v.([]interface{}) + + for _, config := range storageConfigs { + storageConfig := config.(map[string]interface{}) + + logs.ApplicationLogs.AzureBlobStorage = &web.AzureBlobStorageApplicationLogsConfig{ + Level: web.LogLevel(storageConfig["level"].(string)), + SasURL: utils.String(storageConfig["sas_url"].(string)), + RetentionInDays: utils.Int32(int32(storageConfig["retention_in_days"].(int))), + } + } + } + } + } + + if v, ok := config["http_logs"]; ok { + httpLogsConfigs := v.([]interface{}) + + for _, config := range httpLogsConfigs { + httpLogsConfig := config.(map[string]interface{}) + + logs.HTTPLogs = &web.HTTPLogsConfig{} + + if v, ok := httpLogsConfig["file_system"]; ok { + fileSystemConfigs := v.([]interface{}) + + for _, config := range fileSystemConfigs { + fileSystemConfig := config.(map[string]interface{}) + + logs.HTTPLogs.FileSystem = &web.FileSystemHTTPLogsConfig{ + RetentionInMb: utils.Int32(int32(fileSystemConfig["retention_in_mb"].(int))), + RetentionInDays: utils.Int32(int32(fileSystemConfig["retention_in_days"].(int))), + Enabled: utils.Bool(true), + } + } + } + + if v, ok := httpLogsConfig["azure_blob_storage"]; ok { + storageConfigs := v.([]interface{}) + + for _, config := range storageConfigs { + storageConfig := config.(map[string]interface{}) + + logs.HTTPLogs.AzureBlobStorage = &web.AzureBlobStorageHTTPLogsConfig{ + SasURL: utils.String(storageConfig["sas_url"].(string)), + RetentionInDays: utils.Int32(int32(storageConfig["retention_in_days"].(int))), + Enabled: utils.Bool(true), + } + } + } + } + } + + return logs +} + +func expandAppServiceIdentity(input []interface{}) *web.ManagedServiceIdentity { + if len(input) == 0 { + return nil + } + identity := input[0].(map[string]interface{}) + identityType := web.ManagedServiceIdentityType(identity["type"].(string)) + + identityIds := make(map[string]*web.ManagedServiceIdentityUserAssignedIdentitiesValue) + for _, id := range identity["identity_ids"].([]interface{}) { + identityIds[id.(string)] = &web.ManagedServiceIdentityUserAssignedIdentitiesValue{} + } + + managedServiceIdentity := web.ManagedServiceIdentity{ + Type: identityType, + } + + if managedServiceIdentity.Type == web.ManagedServiceIdentityTypeUserAssigned || managedServiceIdentity.Type == SystemAssignedUserAssigned { + managedServiceIdentity.UserAssignedIdentities = identityIds + } + + return &managedServiceIdentity +} + +func flattenAppServiceIdentity(identity *web.ManagedServiceIdentity) []interface{} { + if identity == nil { + return make([]interface{}, 0) + } + + principalId := "" + if identity.PrincipalID != nil { + principalId = *identity.PrincipalID + } + + tenantId := "" + if identity.TenantID != nil { + tenantId = *identity.TenantID + } + + identityIds := make([]string, 0) + if identity.UserAssignedIdentities != nil { + for key := range identity.UserAssignedIdentities { + identityIds = append(identityIds, key) + } + } + + return []interface{}{ + map[string]interface{}{ + "identity_ids": identityIds, + "principal_id": principalId, + "tenant_id": tenantId, + "type": string(identity.Type), + }, + } +} + +func expandAppServiceSiteConfig(input interface{}) (*web.SiteConfig, error) { + configs := input.([]interface{}) + siteConfig := &web.SiteConfig{} + + if len(configs) == 0 { + return siteConfig, nil + } + + config := configs[0].(map[string]interface{}) + + if v, ok := config["always_on"]; ok { + siteConfig.AlwaysOn = utils.Bool(v.(bool)) + } + + if v, ok := config["app_command_line"]; ok { + siteConfig.AppCommandLine = utils.String(v.(string)) + } + + if v, ok := config["default_documents"]; ok { + input := v.([]interface{}) + + documents := make([]string, 0) + for _, document := range input { + documents = append(documents, document.(string)) + } + + siteConfig.DefaultDocuments = &documents + } + + if v, ok := config["dotnet_framework_version"]; ok { + siteConfig.NetFrameworkVersion = utils.String(v.(string)) + } + + if v, ok := config["java_version"]; ok { + siteConfig.JavaVersion = utils.String(v.(string)) + } + + if v, ok := config["java_container"]; ok { + siteConfig.JavaContainer = utils.String(v.(string)) + } + + if v, ok := config["java_container_version"]; ok { + siteConfig.JavaContainerVersion = utils.String(v.(string)) + } + + if v, ok := config["linux_fx_version"]; ok { + siteConfig.LinuxFxVersion = utils.String(v.(string)) + } + + if v, ok := config["windows_fx_version"]; ok { + siteConfig.WindowsFxVersion = utils.String(v.(string)) + } + + if v, ok := config["http2_enabled"]; ok { + siteConfig.HTTP20Enabled = utils.Bool(v.(bool)) + } + + if v, ok := config["ip_restriction"]; ok { + ipSecurityRestrictions := v.(interface{}) + restrictions, err := expandAppServiceIpRestriction(ipSecurityRestrictions) + if err != nil { + return siteConfig, err + } + siteConfig.IPSecurityRestrictions = &restrictions + } + + if v, ok := config["scm_use_main_ip_restriction"]; ok { + siteConfig.ScmIPSecurityRestrictionsUseMain = utils.Bool(v.(bool)) + } + + if v, ok := config["scm_ip_restriction"]; ok { + scmIPSecurityRestrictions := v.([]interface{}) + scmRestrictions, err := expandAppServiceIpRestriction(scmIPSecurityRestrictions) + if err != nil { + return siteConfig, err + } + siteConfig.ScmIPSecurityRestrictions = &scmRestrictions + } + + if v, ok := config["local_mysql_enabled"]; ok { + siteConfig.LocalMySQLEnabled = utils.Bool(v.(bool)) + } + + if v, ok := config["managed_pipeline_mode"]; ok { + siteConfig.ManagedPipelineMode = web.ManagedPipelineMode(v.(string)) + } + + if v, ok := config["php_version"]; ok { + siteConfig.PhpVersion = utils.String(v.(string)) + } + + if v, ok := config["python_version"]; ok { + siteConfig.PythonVersion = utils.String(v.(string)) + } + + if v, ok := config["remote_debugging_enabled"]; ok { + siteConfig.RemoteDebuggingEnabled = utils.Bool(v.(bool)) + } + + if v, ok := config["remote_debugging_version"]; ok { + siteConfig.RemoteDebuggingVersion = utils.String(v.(string)) + } + + if v, ok := config["use_32_bit_worker_process"]; ok { + siteConfig.Use32BitWorkerProcess = utils.Bool(v.(bool)) + } + + if v, ok := config["websockets_enabled"]; ok { + siteConfig.WebSocketsEnabled = utils.Bool(v.(bool)) + } + + if v, ok := config["scm_type"]; ok { + siteConfig.ScmType = web.ScmType(v.(string)) + } + + if v, ok := config["ftps_state"]; ok { + siteConfig.FtpsState = web.FtpsState(v.(string)) + } + + if v, ok := config["health_check_path"]; ok { + siteConfig.HealthCheckPath = utils.String(v.(string)) + } + + if v, ok := config["min_tls_version"]; ok { + siteConfig.MinTLSVersion = web.SupportedTLSVersions(v.(string)) + } + + if v, ok := config["cors"]; ok { + corsSettings := v.(interface{}) + expand := azure.ExpandWebCorsSettings(corsSettings) + siteConfig.Cors = &expand + } + + if v, ok := config["auto_swap_slot_name"]; ok { + siteConfig.AutoSwapSlotName = utils.String(v.(string)) + } + + return siteConfig, nil +} + +func flattenAppServiceSiteConfig(input *web.SiteConfig) []interface{} { + results := make([]interface{}, 0) + result := make(map[string]interface{}) + + if input == nil { + log.Printf("[DEBUG] SiteConfig is nil") + return results + } + + if input.AlwaysOn != nil { + result["always_on"] = *input.AlwaysOn + } + + if input.AppCommandLine != nil { + result["app_command_line"] = *input.AppCommandLine + } + + documents := make([]string, 0) + if s := input.DefaultDocuments; s != nil { + documents = *s + } + result["default_documents"] = documents + + if input.NetFrameworkVersion != nil { + result["dotnet_framework_version"] = *input.NetFrameworkVersion + } + + if input.JavaVersion != nil { + result["java_version"] = *input.JavaVersion + } + + if input.JavaContainer != nil { + result["java_container"] = *input.JavaContainer + } + + if input.JavaContainerVersion != nil { + result["java_container_version"] = *input.JavaContainerVersion + } + + if input.LocalMySQLEnabled != nil { + result["local_mysql_enabled"] = *input.LocalMySQLEnabled + } + + if input.HTTP20Enabled != nil { + result["http2_enabled"] = *input.HTTP20Enabled + } + + result["ip_restriction"] = flattenAppServiceIpRestriction(input.IPSecurityRestrictions) + + if input.ScmIPSecurityRestrictionsUseMain != nil { + result["scm_use_main_ip_restriction"] = *input.ScmIPSecurityRestrictionsUseMain + } + + result["scm_ip_restriction"] = flattenAppServiceIpRestriction(input.ScmIPSecurityRestrictions) + + result["managed_pipeline_mode"] = string(input.ManagedPipelineMode) + + if input.PhpVersion != nil { + result["php_version"] = *input.PhpVersion + } + + if input.PythonVersion != nil { + result["python_version"] = *input.PythonVersion + } + + if input.RemoteDebuggingEnabled != nil { + result["remote_debugging_enabled"] = *input.RemoteDebuggingEnabled + } + + if input.RemoteDebuggingVersion != nil { + result["remote_debugging_version"] = *input.RemoteDebuggingVersion + } + + if input.Use32BitWorkerProcess != nil { + result["use_32_bit_worker_process"] = *input.Use32BitWorkerProcess + } + + if input.WebSocketsEnabled != nil { + result["websockets_enabled"] = *input.WebSocketsEnabled + } + + if input.LinuxFxVersion != nil { + result["linux_fx_version"] = *input.LinuxFxVersion + } + + if input.WindowsFxVersion != nil { + result["windows_fx_version"] = *input.WindowsFxVersion + } + + result["scm_type"] = string(input.ScmType) + result["ftps_state"] = string(input.FtpsState) + + if input.HealthCheckPath != nil { + result["health_check_path"] = *input.HealthCheckPath + } + + result["min_tls_version"] = string(input.MinTLSVersion) + + result["cors"] = azure.FlattenWebCorsSettings(input.Cors) + + if input.AutoSwapSlotName != nil { + result["auto_swap_slot_name"] = *input.AutoSwapSlotName + } + + return append(results, result) +} + +func flattenAppServiceIpRestriction(input *[]web.IPSecurityRestriction) []interface{} { + restrictions := make([]interface{}, 0) + + if input == nil { + return restrictions + } + + for _, v := range *input { + restriction := make(map[string]interface{}) + if ip := v.IPAddress; ip != nil { + if *ip == "Any" { + continue + } else { + restriction["ip_address"] = *ip + } + } + + if subnetId := v.VnetSubnetResourceID; subnetId != nil { + restriction["virtual_network_subnet_id"] = subnetId + restriction["subnet_id"] = subnetId + } + + if name := v.Name; name != nil { + restriction["name"] = *name + } + + if priority := v.Priority; priority != nil { + restriction["priority"] = *priority + } + + if action := v.Action; action != nil { + restriction["action"] = *action + } + + restrictions = append(restrictions, restriction) + } + + return restrictions +} + +func expandAppServiceStorageAccounts(input []interface{}) map[string]*web.AzureStorageInfoValue { + output := make(map[string]*web.AzureStorageInfoValue, len(input)) + + for _, v := range input { + vals := v.(map[string]interface{}) + + saName := vals["name"].(string) + saType := vals["type"].(string) + saAccountName := vals["account_name"].(string) + saShareName := vals["share_name"].(string) + saAccessKey := vals["access_key"].(string) + saMountPath := vals["mount_path"].(string) + + output[saName] = &web.AzureStorageInfoValue{ + Type: web.AzureStorageType(saType), + AccountName: utils.String(saAccountName), + ShareName: utils.String(saShareName), + AccessKey: utils.String(saAccessKey), + MountPath: utils.String(saMountPath), + } + } + + return output +} + +func flattenAppServiceStorageAccounts(input map[string]*web.AzureStorageInfoValue) []interface{} { + results := make([]interface{}, 0) + + for k, v := range input { + result := make(map[string]interface{}) + result["name"] = k + result["type"] = string(v.Type) + if v.AccountName != nil { + result["account_name"] = *v.AccountName + } + if v.ShareName != nil { + result["share_name"] = *v.ShareName + } + if v.AccessKey != nil { + result["access_key"] = *v.AccessKey + } + if v.MountPath != nil { + result["mount_path"] = *v.MountPath + } + results = append(results, result) + } + + return results +} + +func expandAppServiceIpRestriction(input interface{}) ([]web.IPSecurityRestriction, error) { + restrictions := make([]web.IPSecurityRestriction, 0) + + for _, r := range input.([]interface{}) { + if r == nil { + continue + } + + restriction := r.(map[string]interface{}) + + ipAddress := restriction["ip_address"].(string) + vNetSubnetID := "" + + if subnetID, ok := restriction["subnet_id"]; ok { + vNetSubnetID = subnetID.(string) + } + + if subnetID, ok := restriction["virtual_network_subnet_id"]; ok { + vNetSubnetID = subnetID.(string) + } + + name := restriction["name"].(string) + priority := restriction["priority"].(int) + action := restriction["action"].(string) + + if vNetSubnetID != "" && ipAddress != "" { + return nil, fmt.Errorf("only one of `ip_address` or `virtual_network_subnet_id` can be set for an IP restriction") + } + + if vNetSubnetID == "" && ipAddress == "" { + return nil, fmt.Errorf("one of `ip_address` or `virtual_network_subnet_id` must be set for an IP restriction") + } + + ipSecurityRestriction := web.IPSecurityRestriction{} + if ipAddress == "Any" { + continue + } + + if ipAddress != "" { + ipSecurityRestriction.IPAddress = &ipAddress + } + + if vNetSubnetID != "" { + ipSecurityRestriction.VnetSubnetResourceID = &vNetSubnetID + } + + if name != "" { + ipSecurityRestriction.Name = &name + } + + if priority != 0 { + ipSecurityRestriction.Priority = utils.Int32(int32(priority)) + } + + if action != "" { + ipSecurityRestriction.Action = &action + } + + restrictions = append(restrictions, ipSecurityRestriction) } - return warnings, errors + return restrictions, nil } diff --git a/azurerm/helpers/azure/app_service_schedule_backup.go b/azurerm/internal/services/web/app_service_schedule_backup.go similarity index 96% rename from azurerm/helpers/azure/app_service_schedule_backup.go rename to azurerm/internal/services/web/app_service_schedule_backup.go index d26a3520e979..1618f74964d2 100644 --- a/azurerm/helpers/azure/app_service_schedule_backup.go +++ b/azurerm/internal/services/web/app_service_schedule_backup.go @@ -1,4 +1,4 @@ -package azure +package web import ( "time" @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/schema" ) -func SchemaAppServiceBackup() *schema.Schema { +func schemaAppServiceBackup() *schema.Schema { return &schema.Schema{ Type: schema.TypeList, Optional: true, @@ -87,7 +87,7 @@ func SchemaAppServiceBackup() *schema.Schema { } } -func ExpandAppServiceBackup(input []interface{}) *web.BackupRequest { +func expandAppServiceBackup(input []interface{}) *web.BackupRequest { if len(input) == 0 { return nil } @@ -138,7 +138,7 @@ func ExpandAppServiceBackup(input []interface{}) *web.BackupRequest { return request } -func FlattenAppServiceBackup(input *web.BackupRequestProperties) []interface{} { +func flattenAppServiceBackup(input *web.BackupRequestProperties) []interface{} { if input == nil { return []interface{}{} } diff --git a/azurerm/internal/services/web/app_service_site_source_control.go b/azurerm/internal/services/web/app_service_site_source_control.go new file mode 100644 index 000000000000..af5cdb5ac18f --- /dev/null +++ b/azurerm/internal/services/web/app_service_site_source_control.go @@ -0,0 +1,130 @@ +package web + +import ( + "log" + + "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2019-08-01/web" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func schemaAppServiceSiteSourceControl() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Computed: true, + ConflictsWith: []string{"site_config.0.scm_type"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "repo_url": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "branch": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "manual_integration": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + + "use_mercurial": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + + "rollback_enabled": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + }, + }, + } +} + +func schemaDataSourceAppServiceSiteSourceControl() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "repo_url": { + Type: schema.TypeString, + Computed: true, + }, + + "branch": { + Type: schema.TypeString, + Computed: true, + }, + + "manual_integration": { + Type: schema.TypeBool, + Computed: true, + }, + + "use_mercurial": { + Type: schema.TypeBool, + Computed: true, + }, + + "rollback_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + } +} + +func expandAppServiceSiteSourceControl(d *schema.ResourceData) *web.SiteSourceControlProperties { + sourceControlRaw := d.Get("source_control").([]interface{}) + sourceControl := sourceControlRaw[0].(map[string]interface{}) + + result := &web.SiteSourceControlProperties{ + RepoURL: utils.String(sourceControl["repo_url"].(string)), + Branch: utils.String(sourceControl["branch"].(string)), + IsManualIntegration: utils.Bool(sourceControl["manual_integration"].(bool)), + IsMercurial: utils.Bool(sourceControl["use_mercurial"].(bool)), + DeploymentRollbackEnabled: utils.Bool(sourceControl["rollback_enabled"].(bool)), + } + + return result +} + +func flattenAppServiceSourceControl(input *web.SiteSourceControlProperties) []interface{} { + results := make([]interface{}, 0) + result := make(map[string]interface{}) + + if input == nil { + log.Printf("[DEBUG] SiteSourceControlProperties is nil") + return results + } + + if input.RepoURL != nil && *input.RepoURL != "" { + result["repo_url"] = *input.RepoURL + } + + if input.Branch != nil && *input.Branch != "" { + result["branch"] = *input.Branch + } else { + result["branch"] = "master" + } + + result["use_mercurial"] = *input.IsMercurial + + result["manual_integration"] = *input.IsManualIntegration + + result["rollback_enabled"] = *input.DeploymentRollbackEnabled + + return append(results, result) +} diff --git a/azurerm/internal/services/web/data_source_app_service.go b/azurerm/internal/services/web/data_source_app_service.go index f4c7744eb2b0..eeeae1115726 100644 --- a/azurerm/internal/services/web/data_source_app_service.go +++ b/azurerm/internal/services/web/data_source_app_service.go @@ -35,7 +35,7 @@ func dataSourceArmAppService() *schema.Resource { Computed: true, }, - "site_config": azure.SchemaAppServiceDataSourceSiteConfig(), + "site_config": schemaAppServiceDataSourceSiteConfig(), "client_affinity_enabled": { Type: schema.TypeBool, @@ -122,22 +122,7 @@ func dataSourceArmAppService() *schema.Resource { Computed: true, }, - "source_control": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "repo_url": { - Type: schema.TypeString, - Computed: true, - }, - "branch": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, + "source_control": schemaDataSourceAppServiceSiteSourceControl(), }, } } @@ -216,7 +201,7 @@ func dataSourceArmAppServiceRead(d *schema.ResourceData, meta interface{}) error return err } - siteConfig := azure.FlattenAppServiceSiteConfig(configResp.SiteConfig) + siteConfig := flattenAppServiceSiteConfig(configResp.SiteConfig) if err := d.Set("site_config", siteConfig); err != nil { return err } diff --git a/azurerm/internal/services/web/data_source_function_app.go b/azurerm/internal/services/web/data_source_function_app.go index 1c75bdd9dc6d..789535927382 100644 --- a/azurerm/internal/services/web/data_source_function_app.go +++ b/azurerm/internal/services/web/data_source_function_app.go @@ -78,6 +78,21 @@ func dataSourceArmFunctionApp() *schema.Resource { Computed: true, }, + "os_type": { + Type: schema.TypeString, + Computed: true, + }, + + "outbound_ip_addresses": { + Type: schema.TypeString, + Computed: true, + }, + + "possible_outbound_ip_addresses": { + Type: schema.TypeString, + Computed: true, + }, + "site_credential": { Type: schema.TypeList, Computed: true, @@ -96,20 +111,9 @@ func dataSourceArmFunctionApp() *schema.Resource { }, }, - "os_type": { - Type: schema.TypeString, - Computed: true, - }, + "site_config": schemaFunctionAppDataSourceSiteConfig(), - "outbound_ip_addresses": { - Type: schema.TypeString, - Computed: true, - }, - - "possible_outbound_ip_addresses": { - Type: schema.TypeString, - Computed: true, - }, + "source_control": schemaDataSourceAppServiceSiteSourceControl(), "tags": tags.Schema(), }, @@ -145,6 +149,11 @@ func dataSourceArmFunctionAppRead(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("Error making Read request on AzureRM Function App ConnectionStrings %q: %+v", name, err) } + scmResp, err := client.GetSourceControl(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("Error making Read request on AzureRM App Service Source Control %q: %+v", name, err) + } + siteCredFuture, err := client.ListPublishingCredentials(ctx, resourceGroup, name) if err != nil { return err @@ -157,6 +166,10 @@ func dataSourceArmFunctionAppRead(d *schema.ResourceData, meta interface{}) erro if err != nil { return fmt.Errorf("Error making Read request on AzureRM App Service Site Credential %q: %+v", name, err) } + configResp, err := client.GetConfiguration(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("Error making Read request on AzureRM Function App Configuration %q: %+v", name, err) + } d.SetId(*resp.ID) @@ -196,5 +209,15 @@ func dataSourceArmFunctionAppRead(d *schema.ResourceData, meta interface{}) erro return err } + siteConfig := flattenFunctionAppSiteConfig(configResp.SiteConfig) + if err = d.Set("site_config", siteConfig); err != nil { + return err + } + + scm := flattenAppServiceSourceControl(scmResp.SiteSourceControlProperties) + if err := d.Set("source_control", scm); err != nil { + return err + } + return tags.FlattenAndSet(d, resp.Tags) } diff --git a/azurerm/internal/services/web/function_app.go b/azurerm/internal/services/web/function_app.go new file mode 100644 index 000000000000..b5500c879fe6 --- /dev/null +++ b/azurerm/internal/services/web/function_app.go @@ -0,0 +1,491 @@ +package web + +import ( + "context" + "fmt" + "log" + "strings" + + "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2019-08-01/web" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func schemaAppServiceFunctionAppSiteConfig() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "always_on": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "cors": azure.SchemaWebCorsSettings(), + + "ftps_state": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.AllAllowed), + string(web.Disabled), + string(web.FtpsOnly), + }, false), + }, + + "http2_enabled": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "ip_restriction": schemaAppServiceIpRestriction(), + + "linux_fx_version": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "min_tls_version": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.OneFullStopZero), + string(web.OneFullStopOne), + string(web.OneFullStopTwo), + }, false), + }, + + "pre_warmed_instance_count": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(0, 10), + }, + + "scm_ip_restriction": schemaAppServiceIpRestriction(), + + "scm_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.ScmTypeBitbucketGit), + string(web.ScmTypeBitbucketHg), + string(web.ScmTypeCodePlexGit), + string(web.ScmTypeCodePlexHg), + string(web.ScmTypeDropbox), + string(web.ScmTypeExternalGit), + string(web.ScmTypeExternalHg), + string(web.ScmTypeGitHub), + string(web.ScmTypeLocalGit), + string(web.ScmTypeNone), + string(web.ScmTypeOneDrive), + string(web.ScmTypeTfs), + string(web.ScmTypeVSO), + string(web.ScmTypeVSTSRM), + }, false), + }, + + "scm_use_main_ip_restriction": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "use_32_bit_worker_process": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "websockets_enabled": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + // The following is only used for "slots" + "auto_swap_slot_name": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + } +} + +func schemaFunctionAppDataSourceSiteConfig() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "always_on": { + Type: schema.TypeBool, + Computed: true, + }, + + "cors": azure.SchemaWebCorsSettings(), + + "use_32_bit_worker_process": { + Type: schema.TypeBool, + Computed: true, + }, + + "websockets_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + + "linux_fx_version": { + Type: schema.TypeString, + Computed: true, + }, + + "http2_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + + "ip_restriction": schemaAppServiceDataSourceIpRestriction(), + + "min_tls_version": { + Type: schema.TypeString, + Computed: true, + }, + + "ftps_state": { + Type: schema.TypeString, + Computed: true, + }, + + "pre_warmed_instance_count": { + Type: schema.TypeInt, + Computed: true, + }, + + // The following is only used for "slots" + "auto_swap_slot_name": { + Type: schema.TypeString, + Computed: true, + }, + + "scm_ip_restriction": schemaAppServiceDataSourceIpRestriction(), + + "scm_type": { + Type: schema.TypeString, + Computed: true, + }, + + "scm_use_main_ip_restriction": { + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + } +} + +func getBasicFunctionAppAppSettings(d *schema.ResourceData, appServiceTier, endpointSuffix string) ([]web.NameValuePair, error) { + // TODO: This is a workaround since there are no public Functions API + // You may track the API request here: https://github.com/Azure/azure-rest-api-specs/issues/3750 + dashboardPropName := "AzureWebJobsDashboard" + storagePropName := "AzureWebJobsStorage" + functionVersionPropName := "FUNCTIONS_EXTENSION_VERSION" + + contentSharePropName := "WEBSITE_CONTENTSHARE" + contentFileConnStringPropName := "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING" + + // TODO 3.0 - remove this logic for determining which storage account connection string to use + storageConnection := "" + if v, ok := d.GetOk("storage_connection_string"); ok { + storageConnection = v.(string) + } + + storageAccount := "" + if v, ok := d.GetOk("storage_account_name"); ok { + storageAccount = v.(string) + } + + connectionString := "" + if v, ok := d.GetOk("storage_account_access_key"); ok { + connectionString = v.(string) + } + + if storageConnection == "" && storageAccount == "" && connectionString == "" { + return nil, fmt.Errorf("one of `storage_connection_string` or `storage_account_name` and `storage_account_access_key` must be specified") + } + + if (storageAccount == "" && connectionString != "") || (storageAccount != "" && connectionString == "") { + return nil, fmt.Errorf("both `storage_account_name` and `storage_account_access_key` must be specified") + } + + if connectionString != "" && storageAccount != "" { + storageConnection = fmt.Sprintf("DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s;EndpointSuffix=%s", storageAccount, connectionString, endpointSuffix) + } + + functionVersion := d.Get("version").(string) + contentShare := strings.ToLower(d.Get("name").(string)) + "-content" + + basicSettings := []web.NameValuePair{ + {Name: &storagePropName, Value: &storageConnection}, + {Name: &functionVersionPropName, Value: &functionVersion}, + } + + if d.Get("enable_builtin_logging").(bool) { + basicSettings = append(basicSettings, web.NameValuePair{ + Name: &dashboardPropName, + Value: &storageConnection, + }) + } + + consumptionSettings := []web.NameValuePair{ + {Name: &contentSharePropName, Value: &contentShare}, + {Name: &contentFileConnStringPropName, Value: &storageConnection}, + } + + // On consumption and premium plans include WEBSITE_CONTENT components, unless it's a Linux consumption plan + // (see https://github.com/Azure/azure-functions-python-worker/issues/598) + if (strings.EqualFold(appServiceTier, "dynamic") || strings.EqualFold(appServiceTier, "elasticpremium")) && + !strings.EqualFold(d.Get("os_type").(string), "linux") { + return append(basicSettings, consumptionSettings...), nil + } + + return basicSettings, nil +} + +func getFunctionAppServiceTier(ctx context.Context, appServicePlanId string, meta interface{}) (string, error) { + id, err := ParseAppServicePlanID(appServicePlanId) + if err != nil { + return "", fmt.Errorf("[ERROR] Unable to parse App Service Plan ID %q: %+v", appServicePlanId, err) + } + + log.Printf("[DEBUG] Retrieving App Service Plan %q (Resource Group %q)", id.Name, id.ResourceGroup) + + appServicePlansClient := meta.(*clients.Client).Web.AppServicePlansClient + appServicePlan, err := appServicePlansClient.Get(ctx, id.ResourceGroup, id.Name) + if err != nil { + return "", fmt.Errorf("[ERROR] Could not retrieve App Service Plan ID %q: %+v", appServicePlanId, err) + } + + if sku := appServicePlan.Sku; sku != nil { + if tier := sku.Tier; tier != nil { + return *tier, nil + } + } + return "", fmt.Errorf("No `sku` block was returned for App Service Plan ID %q", appServicePlanId) +} + +func expandFunctionAppAppSettings(d *schema.ResourceData, appServiceTier, endpointSuffix string) (map[string]*string, error) { + output := expandAppServiceAppSettings(d) + + basicAppSettings, err := getBasicFunctionAppAppSettings(d, appServiceTier, endpointSuffix) + if err != nil { + return nil, err + } + for _, p := range basicAppSettings { + output[*p.Name] = p.Value + } + + return output, nil +} + +func expandFunctionAppSiteConfig(d *schema.ResourceData) (web.SiteConfig, error) { + configs := d.Get("site_config").([]interface{}) + siteConfig := web.SiteConfig{} + + if len(configs) == 0 { + return siteConfig, nil + } + + config := configs[0].(map[string]interface{}) + + if v, ok := config["always_on"]; ok { + siteConfig.AlwaysOn = utils.Bool(v.(bool)) + } + + if v, ok := config["use_32_bit_worker_process"]; ok { + siteConfig.Use32BitWorkerProcess = utils.Bool(v.(bool)) + } + + if v, ok := config["websockets_enabled"]; ok { + siteConfig.WebSocketsEnabled = utils.Bool(v.(bool)) + } + + if v, ok := config["linux_fx_version"]; ok { + siteConfig.LinuxFxVersion = utils.String(v.(string)) + } + + if v, ok := config["cors"]; ok { + corsSettings := v.(interface{}) + expand := azure.ExpandWebCorsSettings(corsSettings) + siteConfig.Cors = &expand + } + + if v, ok := config["http2_enabled"]; ok { + siteConfig.HTTP20Enabled = utils.Bool(v.(bool)) + } + + if v, ok := config["ip_restriction"]; ok { + ipSecurityRestrictions := v.(interface{}) + restrictions, err := expandAppServiceIpRestriction(ipSecurityRestrictions) + if err != nil { + return siteConfig, err + } + siteConfig.IPSecurityRestrictions = &restrictions + } + + if v, ok := config["scm_use_main_ip_restriction"]; ok { + siteConfig.ScmIPSecurityRestrictionsUseMain = utils.Bool(v.(bool)) + } + + if v, ok := config["scm_ip_restriction"]; ok { + scmIPSecurityRestrictions := v.([]interface{}) + scmRestrictions, err := expandAppServiceIpRestriction(scmIPSecurityRestrictions) + if err != nil { + return siteConfig, err + } + siteConfig.ScmIPSecurityRestrictions = &scmRestrictions + } + + if v, ok := config["min_tls_version"]; ok { + siteConfig.MinTLSVersion = web.SupportedTLSVersions(v.(string)) + } + + if v, ok := config["ftps_state"]; ok { + siteConfig.FtpsState = web.FtpsState(v.(string)) + } + + if v, ok := config["pre_warmed_instance_count"]; ok { + siteConfig.PreWarmedInstanceCount = utils.Int32(int32(v.(int))) + } + + if v, ok := config["scm_type"]; ok { + siteConfig.ScmType = web.ScmType(v.(string)) + } + + // This optional parameter can only present in "slot" resources + if v, ok := config["auto_swap_slot_name"]; ok { + siteConfig.AutoSwapSlotName = utils.String(v.(string)) + } + + return siteConfig, nil +} + +func flattenFunctionAppSiteConfig(input *web.SiteConfig) []interface{} { + results := make([]interface{}, 0) + result := make(map[string]interface{}) + + if input == nil { + log.Printf("[DEBUG] SiteConfig is nil") + return results + } + + if input.AlwaysOn != nil { + result["always_on"] = *input.AlwaysOn + } + + if input.Use32BitWorkerProcess != nil { + result["use_32_bit_worker_process"] = *input.Use32BitWorkerProcess + } + + if input.WebSocketsEnabled != nil { + result["websockets_enabled"] = *input.WebSocketsEnabled + } + + if input.LinuxFxVersion != nil { + result["linux_fx_version"] = *input.LinuxFxVersion + } + + if input.HTTP20Enabled != nil { + result["http2_enabled"] = *input.HTTP20Enabled + } + + if input.PreWarmedInstanceCount != nil { + result["pre_warmed_instance_count"] = *input.PreWarmedInstanceCount + } + + result["ip_restriction"] = flattenAppServiceIpRestriction(input.IPSecurityRestrictions) + + if input.ScmIPSecurityRestrictionsUseMain != nil { + result["scm_use_main_ip_restriction"] = *input.ScmIPSecurityRestrictionsUseMain + } + + result["scm_ip_restriction"] = flattenAppServiceIpRestriction(input.ScmIPSecurityRestrictions) + + result["min_tls_version"] = string(input.MinTLSVersion) + result["ftps_state"] = string(input.FtpsState) + + result["cors"] = azure.FlattenWebCorsSettings(input.Cors) + + if input.AutoSwapSlotName != nil { + result["auto_swap_slot_name"] = *input.AutoSwapSlotName + } + + results = append(results, result) + return results +} + +func expandFunctionAppConnectionStrings(d *schema.ResourceData) map[string]*web.ConnStringValueTypePair { + input := d.Get("connection_string").(*schema.Set).List() + output := make(map[string]*web.ConnStringValueTypePair, len(input)) + + for _, v := range input { + vals := v.(map[string]interface{}) + + csName := vals["name"].(string) + csType := vals["type"].(string) + csValue := vals["value"].(string) + + output[csName] = &web.ConnStringValueTypePair{ + Value: utils.String(csValue), + Type: web.ConnectionStringType(csType), + } + } + + return output +} + +func flattenFunctionAppConnectionStrings(input map[string]*web.ConnStringValueTypePair) interface{} { + results := make([]interface{}, 0) + + for k, v := range input { + result := make(map[string]interface{}) + result["name"] = k + result["type"] = string(v.Type) + result["value"] = *v.Value + results = append(results, result) + } + + return results +} + +func flattenFunctionAppSiteCredential(input *web.UserProperties) []interface{} { + results := make([]interface{}, 0) + result := make(map[string]interface{}) + + if input == nil { + log.Printf("[DEBUG] UserProperties is nil") + return results + } + + if input.PublishingUserName != nil { + result["username"] = *input.PublishingUserName + } + + if input.PublishingPassword != nil { + result["password"] = *input.PublishingPassword + } + + return append(results, result) +} diff --git a/azurerm/internal/services/web/parse/app_service.go b/azurerm/internal/services/web/parse/app_service.go new file mode 100644 index 000000000000..620ee0fd5774 --- /dev/null +++ b/azurerm/internal/services/web/parse/app_service.go @@ -0,0 +1,33 @@ +package parse + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type AppServiceResourceID struct { + ResourceGroup string + Name string +} + +func AppServiceID(input string) (*AppServiceResourceID, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, fmt.Errorf("[ERROR] Unable to parse App Service ID %q: %+v", input, err) + } + + appService := AppServiceResourceID{ + ResourceGroup: id.ResourceGroup, + } + + if appService.Name, err = id.PopSegment("sites"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &appService, nil +} diff --git a/azurerm/internal/services/web/parse/app_service_site_source_control.go b/azurerm/internal/services/web/parse/app_service_site_source_control.go new file mode 100644 index 000000000000..6bae9944a5c8 --- /dev/null +++ b/azurerm/internal/services/web/parse/app_service_site_source_control.go @@ -0,0 +1,13 @@ +package parse + +// /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-group/providers/Microsoft.Web/sites/test-app/sourcecontrols/web + +type AppServiceSiteSourceControlId struct { + ResourceGroup string + AppName string + Name string +} + +func AppServiceSiteSourceControlID(input string) (*AppServiceSiteSourceControlId, error) { + return &AppServiceSiteSourceControlId{}, nil +} diff --git a/azurerm/internal/services/web/app_service_test.go b/azurerm/internal/services/web/parse/app_service_test.go similarity index 61% rename from azurerm/internal/services/web/app_service_test.go rename to azurerm/internal/services/web/parse/app_service_test.go index c23294a6a4a1..17d5637cce21 100644 --- a/azurerm/internal/services/web/app_service_test.go +++ b/azurerm/internal/services/web/parse/app_service_test.go @@ -1,4 +1,4 @@ -package web +package parse import ( "testing" @@ -53,7 +53,7 @@ func TestParseAppService(t *testing.T) { for _, v := range testData { t.Logf("[DEBUG] Testing %q", v.Name) - actual, err := ParseAppServiceID(v.Input) + actual, err := AppServiceID(v.Input) if err != nil { if v.Expected == nil { continue @@ -71,49 +71,3 @@ func TestParseAppService(t *testing.T) { } } } - -func TestValidateAppServiceID(t *testing.T) { - cases := []struct { - ID string - Valid bool - }{ - { - ID: "", - Valid: false, - }, - { - ID: "nonsense", - Valid: false, - }, - { - ID: "/subscriptions/00000000-0000-0000-0000-000000000000", - Valid: false, - }, - { - ID: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo", - Valid: false, - }, - { - ID: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/providers/Microsoft.Web", - Valid: false, - }, - { - ID: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/providers/Microsoft.Web/sites/duckduckgo", - Valid: true, - }, - { - ID: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/providers/Microsoft.Web/Sites/duckduckgo", - Valid: false, - }, - } - - for _, tc := range cases { - t.Logf("[DEBUG] Testing Value %q", tc.ID) - _, errors := ValidateAppServiceID(tc.ID, "test") - valid := len(errors) == 0 - - if tc.Valid != valid { - t.Fatalf("Expected %t but got %t", tc.Valid, valid) - } - } -} diff --git a/azurerm/internal/services/web/registration.go b/azurerm/internal/services/web/registration.go index 496a3972595c..6d7372b065ee 100644 --- a/azurerm/internal/services/web/registration.go +++ b/azurerm/internal/services/web/registration.go @@ -41,9 +41,9 @@ func (r Registration) SupportedResources() map[string]*schema.Resource { "azurerm_app_service_hybrid_connection": resourceArmAppServiceHybridConnection(), "azurerm_app_service_plan": resourceArmAppServicePlan(), "azurerm_app_service_slot": resourceArmAppServiceSlot(), + "azurerm_app_service_slot_virtual_network_swift_connection": resourceArmAppServiceSlotVirtualNetworkSwiftConnection(), "azurerm_app_service_source_control_token": resourceArmAppServiceSourceControlToken(), "azurerm_app_service_virtual_network_swift_connection": resourceArmAppServiceVirtualNetworkSwiftConnection(), - "azurerm_app_service_slot_virtual_network_swift_connection": resourceArmAppServiceSlotVirtualNetworkSwiftConnection(), "azurerm_app_service": resourceArmAppService(), "azurerm_function_app": resourceArmFunctionApp(), "azurerm_function_app_slot": resourceArmFunctionAppSlot(), diff --git a/azurerm/internal/services/web/resource_arm_app_service.go b/azurerm/internal/services/web/resource_arm_app_service.go index 8650e2927f85..a3570549f6c2 100644 --- a/azurerm/internal/services/web/resource_arm_app_service.go +++ b/azurerm/internal/services/web/resource_arm_app_service.go @@ -12,7 +12,7 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/web/parse" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/web/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" @@ -28,7 +28,7 @@ func resourceArmAppService() *schema.Resource { Delete: resourceArmAppServiceDelete, Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { - _, err := ParseAppServiceID(id) + _, err := parse.AppServiceID(id) return err }), @@ -47,8 +47,6 @@ func resourceArmAppService() *schema.Resource { ValidateFunc: validate.AppServiceName, }, - "identity": azure.SchemaAppServiceIdentity(), - "resource_group_name": azure.SchemaResourceGroupName(), "location": azure.SchemaLocation(), @@ -58,13 +56,18 @@ func resourceArmAppService() *schema.Resource { Required: true, }, - "site_config": azure.SchemaAppServiceSiteConfig(), - - "auth_settings": azure.SchemaAppServiceAuthSettings(), + "app_settings": { + Type: schema.TypeMap, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, - "logs": azure.SchemaAppServiceLogsConfig(), + "auth_settings": schemaAppServiceAuthSettings(), - "backup": azure.SchemaAppServiceBackup(), + "backup": schemaAppServiceBackup(), "client_affinity_enabled": { Type: schema.TypeBool, @@ -72,35 +75,12 @@ func resourceArmAppService() *schema.Resource { Default: false, }, - "https_only": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "client_cert_enabled": { Type: schema.TypeBool, Optional: true, Default: false, }, - "enabled": { - Type: schema.TypeBool, - Optional: true, - Default: true, - }, - - "app_settings": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - - "storage_account": azure.SchemaAppServiceStorageAccounts(), - "connection_string": { Type: schema.TypeSet, Optional: true, @@ -111,11 +91,7 @@ func resourceArmAppService() *schema.Resource { Type: schema.TypeString, Required: true, }, - "value": { - Type: schema.TypeString, - Required: true, - Sensitive: true, - }, + "type": { Type: schema.TypeString, Required: true, @@ -134,10 +110,38 @@ func resourceArmAppService() *schema.Resource { }, true), DiffSuppressFunc: suppress.CaseDifference, }, + + "value": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + }, }, }, }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "identity": schemaAppServiceIdentity(), + + "https_only": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "logs": schemaAppServiceLogsConfig(), + + "site_config": schemaAppServiceSiteConfig(), + + "storage_account": schemaAppServiceStorageAccounts(), + + "source_control": schemaAppServiceSiteSourceControl(), + "tags": tags.Schema(), "site_credential": { @@ -171,22 +175,6 @@ func resourceArmAppService() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "source_control": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "repo_url": { - Type: schema.TypeString, - Computed: true, - }, - "branch": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, }, } } @@ -200,19 +188,17 @@ func resourceArmAppServiceCreate(d *schema.ResourceData, meta interface{}) error log.Printf("[INFO] preparing arguments for AzureRM App Service creation.") name := d.Get("name").(string) - resGroup := d.Get("resource_group_name").(string) + resourceGroup := d.Get("resource_group_name").(string) - if features.ShouldResourcesBeImported() && d.IsNewResource() { - existing, err := client.Get(ctx, resGroup, name) - if err != nil { - if !utils.ResponseWasNotFound(existing.Response) { - return fmt.Errorf("Error checking for presence of existing App Service %q (Resource Group %q): %s", name, resGroup, err) - } + existing, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of existing App Service %q (Resource Group %q): %s", name, resourceGroup, err) } + } - if existing.ID != nil && *existing.ID != "" { - return tf.ImportAsExistsError("azurerm_app_service", *existing.ID) - } + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_app_service", *existing.ID) } availabilityRequest := web.ResourceNameAvailabilityRequest{ @@ -246,9 +232,9 @@ func resourceArmAppServiceCreate(d *schema.ResourceData, meta interface{}) error httpsOnly := d.Get("https_only").(bool) t := d.Get("tags").(map[string]interface{}) - siteConfig, err := azure.ExpandAppServiceSiteConfig(d.Get("site_config")) + siteConfig, err := expandAppServiceSiteConfig(d.Get("site_config")) if err != nil { - return fmt.Errorf("Error expanding `site_config` for App Service %q (Resource Group %q): %s", name, resGroup, err) + return fmt.Errorf("Error expanding `site_config` for App Service %q (Resource Group %q): %s", name, resourceGroup, err) } siteEnvelope := web.Site{ @@ -264,7 +250,7 @@ func resourceArmAppServiceCreate(d *schema.ResourceData, meta interface{}) error if _, ok := d.GetOk("identity"); ok { appServiceIdentityRaw := d.Get("identity").([]interface{}) - appServiceIdentity := azure.ExpandAppServiceIdentity(appServiceIdentityRaw) + appServiceIdentity := expandAppServiceIdentity(appServiceIdentityRaw) siteEnvelope.Identity = appServiceIdentity } @@ -272,52 +258,72 @@ func resourceArmAppServiceCreate(d *schema.ResourceData, meta interface{}) error siteEnvelope.SiteProperties.ClientCertEnabled = utils.Bool(d.Get("client_cert_enabled").(bool)) - createFuture, err := client.CreateOrUpdate(ctx, resGroup, name, siteEnvelope) + createFuture, err := client.CreateOrUpdate(ctx, resourceGroup, name, siteEnvelope) if err != nil { - return fmt.Errorf("Error creating App Service %q (Resource Group %q): %s", name, resGroup, err) + return fmt.Errorf("Error creating App Service %q (Resource Group %q): %s", name, resourceGroup, err) } err = createFuture.WaitForCompletionRef(ctx, client.Client) if err != nil { - return fmt.Errorf("Error waiting for App Service %q (Resource Group %q) to be created: %s", name, resGroup, err) + return fmt.Errorf("Error waiting for App Service %q (Resource Group %q) to be created: %s", name, resourceGroup, err) } - read, err := client.Get(ctx, resGroup, name) + if _, ok := d.GetOk("source_control"); ok { + if siteConfig.ScmType != "" { + return fmt.Errorf("cannot set source_control parameters when scm_type is set to %q", siteConfig.ScmType) + } + sourceControlProperties := expandAppServiceSiteSourceControl(d) + sourceControl := &web.SiteSourceControl{} + sourceControl.SiteSourceControlProperties = sourceControlProperties + // TODO - Do we need to lock the app for updates? + scFuture, err := client.CreateOrUpdateSourceControl(ctx, resourceGroup, name, *sourceControl) + if err != nil { + return fmt.Errorf("failed to create App Service Source Control for %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + err = scFuture.WaitForCompletionRef(ctx, client.Client) + if err != nil { + return fmt.Errorf("failed waiting for App Service Source Control configuration") + } + } + + read, err := client.Get(ctx, resourceGroup, name) if err != nil { - return fmt.Errorf("Error retrieving App Service %q (Resource Group %q): %s", name, resGroup, err) + return fmt.Errorf("Error retrieving App Service %q (Resource Group %q): %s", name, resourceGroup, err) } - if read.ID == nil { - return fmt.Errorf("Cannot read App Service %q (resource group %q) ID", name, resGroup) + + if read.ID == nil || *read.ID == "" { + return fmt.Errorf("Cannot read App Service %q (resource group %q) ID", name, resourceGroup) } d.SetId(*read.ID) authSettingsRaw := d.Get("auth_settings").([]interface{}) - authSettings := azure.ExpandAppServiceAuthSettings(authSettingsRaw) + authSettings := expandAppServiceAuthSettings(authSettingsRaw) auth := web.SiteAuthSettings{ ID: read.ID, SiteAuthSettingsProperties: &authSettings} - if _, err := client.UpdateAuthSettings(ctx, resGroup, name, auth); err != nil { - return fmt.Errorf("Error updating auth settings for App Service %q (Resource Group %q): %+v", name, resGroup, err) + if _, err := client.UpdateAuthSettings(ctx, resourceGroup, name, auth); err != nil { + return fmt.Errorf("Error updating auth settings for App Service %q (Resource Group %q): %+v", name, resourceGroup, err) } - logsConfig := azure.ExpandAppServiceLogs(d.Get("logs")) + logsConfig := expandAppServiceLogs(d.Get("logs")) logs := web.SiteLogsConfig{ ID: read.ID, SiteLogsConfigProperties: &logsConfig} - if _, err := client.UpdateDiagnosticLogsConfig(ctx, resGroup, name, logs); err != nil { - return fmt.Errorf("Error updating diagnostic logs config for App Service %q (Resource Group %q): %+v", name, resGroup, err) + if _, err := client.UpdateDiagnosticLogsConfig(ctx, resourceGroup, name, logs); err != nil { + return fmt.Errorf("Error updating diagnostic logs config for App Service %q (Resource Group %q): %+v", name, resourceGroup, err) } backupRaw := d.Get("backup").([]interface{}) - if backup := azure.ExpandAppServiceBackup(backupRaw); backup != nil { - _, err = client.UpdateBackupConfiguration(ctx, resGroup, name, *backup) + if backup := expandAppServiceBackup(backupRaw); backup != nil { + _, err = client.UpdateBackupConfiguration(ctx, resourceGroup, name, *backup) if err != nil { - return fmt.Errorf("Error updating Backup Settings for App Service %q (Resource Group %q): %+v", name, resGroup, err) + return fmt.Errorf("Error updating Backup Settings for App Service %q (Resource Group %q): %+v", name, resourceGroup, err) } } @@ -329,7 +335,7 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := ParseAppServiceID(d.Id()) + id, err := parse.AppServiceID(d.Id()) if err != nil { return err } @@ -341,7 +347,7 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error httpsOnly := d.Get("https_only").(bool) t := d.Get("tags").(map[string]interface{}) - siteConfig, err := azure.ExpandAppServiceSiteConfig(d.Get("site_config")) + siteConfig, err := expandAppServiceSiteConfig(d.Get("site_config")) if err != nil { return fmt.Errorf("Error expanding `site_config` for App Service %q (Resource Group %q): %s", id.Name, id.ResourceGroup, err) } @@ -368,9 +374,12 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error return err } - if d.HasChange("site_config") { + // If `source_control` is defined, we need to set site_config.0.scm_type to "None" or we cannot update it + _, hasSourceControl := d.GetOk("source_control.0.repo_url") + + if d.HasChange("site_config") || hasSourceControl { // update the main configuration - siteConfig, err := azure.ExpandAppServiceSiteConfig(d.Get("site_config")) + siteConfig, err := expandAppServiceSiteConfig(d.Get("site_config")) if err != nil { return fmt.Errorf("Error expanding `site_config` for App Service %q (Resource Group %q): %s", id.Name, id.ResourceGroup, err) } @@ -378,14 +387,38 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error SiteConfig: siteConfig, } + if hasSourceControl { + siteConfigResource.SiteConfig.ScmType = "None" + } + if _, err := client.CreateOrUpdateConfiguration(ctx, id.ResourceGroup, id.Name, siteConfigResource); err != nil { return fmt.Errorf("Error updating Configuration for App Service %q: %+v", id.Name, err) } } + if hasSourceControl { + sourceControlProperties := expandAppServiceSiteSourceControl(d) + sourceControl := &web.SiteSourceControl{} + sourceControl.SiteSourceControlProperties = sourceControlProperties + scFuture, err := client.CreateOrUpdateSourceControl(ctx, id.ResourceGroup, id.Name, *sourceControl) + if err != nil { + return fmt.Errorf("failed to update App Service Source Control for %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + + err = scFuture.WaitForCompletionRef(ctx, client.Client) + if err != nil { + return fmt.Errorf("failed waiting for App Service Source Control configuration: %+v", err) + } + + sc, err := client.GetSourceControl(ctx, id.ResourceGroup, id.Name) + if err != nil { + return fmt.Errorf("failed reading back App Service Source Control for %q", *sc.Name) + } + } + if d.HasChange("auth_settings") { authSettingsRaw := d.Get("auth_settings").([]interface{}) - authSettingsProperties := azure.ExpandAppServiceAuthSettings(authSettingsRaw) + authSettingsProperties := expandAppServiceAuthSettings(authSettingsRaw) authSettings := web.SiteAuthSettings{ ID: utils.String(d.Id()), SiteAuthSettingsProperties: &authSettingsProperties, @@ -398,7 +431,7 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error if d.HasChange("backup") { backupRaw := d.Get("backup").([]interface{}) - if backup := azure.ExpandAppServiceBackup(backupRaw); backup != nil { + if backup := expandAppServiceBackup(backupRaw); backup != nil { _, err = client.UpdateBackupConfiguration(ctx, id.ResourceGroup, id.Name, *backup) if err != nil { return fmt.Errorf("Error updating Backup Settings for App Service %q (Resource Group %q): %s", id.Name, id.ResourceGroup, err) @@ -446,7 +479,7 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error // updating the former will clobber the log settings hasLogs := len(d.Get("logs").([]interface{})) > 0 if d.HasChange("logs") || (hasLogs && d.HasChange("app_settings")) { - logs := azure.ExpandAppServiceLogs(d.Get("logs")) + logs := expandAppServiceLogs(d.Get("logs")) logsResource := web.SiteLogsConfig{ ID: utils.String(d.Id()), SiteLogsConfigProperties: &logs, @@ -459,7 +492,7 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error if d.HasChange("storage_account") { storageAccountsRaw := d.Get("storage_account").(*schema.Set).List() - storageAccounts := azure.ExpandAppServiceStorageAccounts(storageAccountsRaw) + storageAccounts := expandAppServiceStorageAccounts(storageAccountsRaw) properties := web.AzureStoragePropertyDictionaryResource{ Properties: storageAccounts, } @@ -488,7 +521,7 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error } appServiceIdentityRaw := d.Get("identity").([]interface{}) - appServiceIdentity := azure.ExpandAppServiceIdentity(appServiceIdentityRaw) + appServiceIdentity := expandAppServiceIdentity(appServiceIdentityRaw) site.Identity = appServiceIdentity future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, site) @@ -510,7 +543,7 @@ func resourceArmAppServiceRead(d *schema.ResourceData, meta interface{}) error { ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := ParseAppServiceID(d.Id()) + id, err := parse.AppServiceID(d.Id()) if err != nil { return err } @@ -619,11 +652,11 @@ func resourceArmAppServiceRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error setting `app_settings`: %s", err) } - if err := d.Set("backup", azure.FlattenAppServiceBackup(backupResp.BackupRequestProperties)); err != nil { + if err := d.Set("backup", flattenAppServiceBackup(backupResp.BackupRequestProperties)); err != nil { return fmt.Errorf("Error setting `backup`: %s", err) } - if err := d.Set("storage_account", azure.FlattenAppServiceStorageAccounts(storageAccountsResp.Properties)); err != nil { + if err := d.Set("storage_account", flattenAppServiceStorageAccounts(storageAccountsResp.Properties)); err != nil { return fmt.Errorf("Error setting `storage_account`: %s", err) } @@ -631,17 +664,17 @@ func resourceArmAppServiceRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error setting `connection_string`: %s", err) } - siteConfig := azure.FlattenAppServiceSiteConfig(configResp.SiteConfig) + siteConfig := flattenAppServiceSiteConfig(configResp.SiteConfig) if err := d.Set("site_config", siteConfig); err != nil { return err } - authSettings := azure.FlattenAppServiceAuthSettings(authResp.SiteAuthSettingsProperties) + authSettings := flattenAppServiceAuthSettings(authResp.SiteAuthSettingsProperties) if err := d.Set("auth_settings", authSettings); err != nil { return fmt.Errorf("Error setting `auth_settings`: %s", err) } - logs := azure.FlattenAppServiceLogs(logsResp.SiteLogsConfigProperties) + logs := flattenAppServiceLogs(logsResp.SiteLogsConfigProperties) if err := d.Set("logs", logs); err != nil { return fmt.Errorf("Error setting `logs`: %s", err) } @@ -656,7 +689,7 @@ func resourceArmAppServiceRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error setting `site_credential`: %s", err) } - identity := azure.FlattenAppServiceIdentity(resp.Identity) + identity := flattenAppServiceIdentity(resp.Identity) if err := d.Set("identity", identity); err != nil { return fmt.Errorf("Error setting `identity`: %s", err) } @@ -669,7 +702,7 @@ func resourceArmAppServiceDelete(d *schema.ResourceData, meta interface{}) error ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := ParseAppServiceID(d.Id()) + id, err := parse.AppServiceID(d.Id()) if err != nil { return err } @@ -688,27 +721,6 @@ func resourceArmAppServiceDelete(d *schema.ResourceData, meta interface{}) error return nil } -func flattenAppServiceSourceControl(input *web.SiteSourceControlProperties) []interface{} { - results := make([]interface{}, 0) - result := make(map[string]interface{}) - - if input == nil { - log.Printf("[DEBUG] SiteSourceControlProperties is nil") - return results - } - - if input.RepoURL != nil { - result["repo_url"] = *input.RepoURL - } - if input.Branch != nil && *input.Branch != "" { - result["branch"] = *input.Branch - } else { - result["branch"] = "master" - } - - return append(results, result) -} - func expandAppServiceAppSettings(d *schema.ResourceData) map[string]*string { input := d.Get("app_settings").(map[string]interface{}) output := make(map[string]*string, len(input)) diff --git a/azurerm/internal/services/web/resource_arm_app_service_active_slot.go b/azurerm/internal/services/web/resource_arm_app_service_active_slot.go index cc92aa51ad1b..1c7a338207be 100644 --- a/azurerm/internal/services/web/resource_arm_app_service_active_slot.go +++ b/azurerm/internal/services/web/resource_arm_app_service_active_slot.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/web/parse" azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" @@ -21,7 +22,7 @@ func resourceArmAppServiceActiveSlot() *schema.Resource { Update: resourceArmAppServiceActiveSlotCreateUpdate, Delete: resourceArmAppServiceActiveSlotDelete, Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { - _, err := ParseAppServiceID(id) + _, err := parse.AppServiceID(id) return err }), @@ -97,7 +98,7 @@ func resourceArmAppServiceActiveSlotRead(d *schema.ResourceData, meta interface{ ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := ParseAppServiceID(d.Id()) + id, err := parse.AppServiceID(d.Id()) if err != nil { return err } diff --git a/azurerm/internal/services/web/resource_arm_app_service_slot.go b/azurerm/internal/services/web/resource_arm_app_service_slot.go index e48726d46bb3..3d656cd9d0a4 100644 --- a/azurerm/internal/services/web/resource_arm_app_service_slot.go +++ b/azurerm/internal/services/web/resource_arm_app_service_slot.go @@ -12,7 +12,6 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" webValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/web/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" @@ -26,6 +25,7 @@ func resourceArmAppServiceSlot() *schema.Resource { Read: resourceArmAppServiceSlotRead, Update: resourceArmAppServiceSlotCreateUpdate, Delete: resourceArmAppServiceSlotDelete, + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { _, err := ParseAppServiceSlotID(id) return err @@ -46,16 +46,17 @@ func resourceArmAppServiceSlot() *schema.Resource { ValidateFunc: webValidate.AppServiceName, }, + "identity": schemaAppServiceIdentity(), + "resource_group_name": azure.SchemaResourceGroupName(), "location": azure.SchemaLocation(), - "identity": azure.SchemaAppServiceIdentity(), - "app_service_name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: webValidate.AppServiceName, }, "app_service_plan_id": { @@ -64,11 +65,11 @@ func resourceArmAppServiceSlot() *schema.Resource { ForceNew: true, }, - "site_config": azure.SchemaAppServiceSiteConfig(), + "site_config": schemaAppServiceSiteConfig(), - "auth_settings": azure.SchemaAppServiceAuthSettings(), + "auth_settings": schemaAppServiceAuthSettings(), - "logs": azure.SchemaAppServiceLogsConfig(), + "logs": schemaAppServiceLogsConfig(), "client_affinity_enabled": { Type: schema.TypeBool, @@ -171,7 +172,7 @@ func resourceArmAppServiceSlotCreateUpdate(d *schema.ResourceData, meta interfac resourceGroup := d.Get("resource_group_name").(string) appServiceName := d.Get("app_service_name").(string) - if features.ShouldResourcesBeImported() && d.IsNewResource() { + if d.IsNewResource() { existing, err := client.GetSlot(ctx, resourceGroup, appServiceName, slot) if err != nil { if !utils.ResponseWasNotFound(existing.Response) { @@ -191,7 +192,7 @@ func resourceArmAppServiceSlotCreateUpdate(d *schema.ResourceData, meta interfac t := d.Get("tags").(map[string]interface{}) affinity := d.Get("client_affinity_enabled").(bool) - siteConfig, err := azure.ExpandAppServiceSiteConfig(d.Get("site_config")) + siteConfig, err := expandAppServiceSiteConfig(d.Get("site_config")) if err != nil { return fmt.Errorf("Error expanding `site_config` for App Service Slot %q (Resource Group %q): %s", slot, resourceGroup, err) } @@ -209,7 +210,7 @@ func resourceArmAppServiceSlotCreateUpdate(d *schema.ResourceData, meta interfac if _, ok := d.GetOk("identity"); ok { appServiceIdentityRaw := d.Get("identity").([]interface{}) - appServiceIdentity := azure.ExpandAppServiceIdentity(appServiceIdentityRaw) + appServiceIdentity := expandAppServiceIdentity(appServiceIdentityRaw) siteEnvelope.Identity = appServiceIdentity } @@ -249,7 +250,7 @@ func resourceArmAppServiceSlotUpdate(d *schema.ResourceData, meta interface{}) e location := azure.NormalizeLocation(d.Get("location").(string)) appServicePlanId := d.Get("app_service_plan_id").(string) - siteConfig, err := azure.ExpandAppServiceSiteConfig(d.Get("site_config")) + siteConfig, err := expandAppServiceSiteConfig(d.Get("site_config")) if err != nil { return fmt.Errorf("Error expanding `site_config` for App Service Slot %q (Resource Group %q): %s", id.Name, id.ResourceGroup, err) } @@ -283,7 +284,7 @@ func resourceArmAppServiceSlotUpdate(d *schema.ResourceData, meta interface{}) e if d.HasChange("site_config") { // update the main configuration - siteConfig, err := azure.ExpandAppServiceSiteConfig(d.Get("site_config")) + siteConfig, err := expandAppServiceSiteConfig(d.Get("site_config")) if err != nil { return fmt.Errorf("Error expanding `site_config` for App Service Slot %q (Resource Group %q): %s", id.Name, id.ResourceGroup, err) } @@ -297,7 +298,7 @@ func resourceArmAppServiceSlotUpdate(d *schema.ResourceData, meta interface{}) e if d.HasChange("auth_settings") { authSettingsRaw := d.Get("auth_settings").([]interface{}) - authSettingsProperties := azure.ExpandAppServiceAuthSettings(authSettingsRaw) + authSettingsProperties := expandAppServiceAuthSettings(authSettingsRaw) authSettings := web.SiteAuthSettings{ ID: utils.String(d.Id()), SiteAuthSettingsProperties: &authSettingsProperties, @@ -327,7 +328,7 @@ func resourceArmAppServiceSlotUpdate(d *schema.ResourceData, meta interface{}) e // updating the former will clobber the log settings hasLogs := len(d.Get("logs").([]interface{})) > 0 if d.HasChange("logs") || (hasLogs && d.HasChange("app_settings")) { - logs := azure.ExpandAppServiceLogs(d.Get("logs")) + logs := expandAppServiceLogs(d.Get("logs")) logsResource := web.SiteLogsConfig{ ID: utils.String(d.Id()), SiteLogsConfigProperties: &logs, @@ -352,7 +353,7 @@ func resourceArmAppServiceSlotUpdate(d *schema.ResourceData, meta interface{}) e if d.HasChange("identity") { appServiceIdentityRaw := d.Get("identity").([]interface{}) - appServiceIdentity := azure.ExpandAppServiceIdentity(appServiceIdentityRaw) + appServiceIdentity := expandAppServiceIdentity(appServiceIdentityRaw) sitePatchResource := web.SitePatchResource{ ID: utils.String(d.Id()), Identity: appServiceIdentity, @@ -468,17 +469,17 @@ func resourceArmAppServiceSlotRead(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("Error setting `connection_string`: %s", err) } - authSettings := azure.FlattenAppServiceAuthSettings(authResp.SiteAuthSettingsProperties) + authSettings := flattenAppServiceAuthSettings(authResp.SiteAuthSettingsProperties) if err := d.Set("auth_settings", authSettings); err != nil { return fmt.Errorf("Error setting `auth_settings`: %s", err) } - logs := azure.FlattenAppServiceLogs(logsResp.SiteLogsConfigProperties) + logs := flattenAppServiceLogs(logsResp.SiteLogsConfigProperties) if err := d.Set("logs", logs); err != nil { return fmt.Errorf("Error setting `logs`: %s", err) } - identity := azure.FlattenAppServiceIdentity(resp.Identity) + identity := flattenAppServiceIdentity(resp.Identity) if err := d.Set("identity", identity); err != nil { return fmt.Errorf("Error setting `identity`: %s", err) } @@ -488,7 +489,7 @@ func resourceArmAppServiceSlotRead(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("Error setting `site_credential`: %s", err) } - siteConfig := azure.FlattenAppServiceSiteConfig(configResp.SiteConfig) + siteConfig := flattenAppServiceSiteConfig(configResp.SiteConfig) if err := d.Set("site_config", siteConfig); err != nil { return fmt.Errorf("Error setting `site_config`: %s", err) } diff --git a/azurerm/internal/services/web/resource_arm_app_service_slot_virtual_network_swift_connection.go b/azurerm/internal/services/web/resource_arm_app_service_slot_virtual_network_swift_connection.go index 2a453dd36e73..441543c07197 100644 --- a/azurerm/internal/services/web/resource_arm_app_service_slot_virtual_network_swift_connection.go +++ b/azurerm/internal/services/web/resource_arm_app_service_slot_virtual_network_swift_connection.go @@ -62,7 +62,7 @@ func resourceArmAppServiceSlotVirtualNetworkSwiftConnectionCreateUpdate(d *schem ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() - appID, err := ParseAppServiceID(d.Get("app_service_id").(string)) + appID, err := parse.AppServiceID(d.Get("app_service_id").(string)) if err != nil { return fmt.Errorf("Error parsing Azure Resource ID %q", appID) } diff --git a/azurerm/internal/services/web/resource_arm_app_service_virtual_network_swift_connection.go b/azurerm/internal/services/web/resource_arm_app_service_virtual_network_swift_connection.go index a50b9827ec09..cf127d422d39 100644 --- a/azurerm/internal/services/web/resource_arm_app_service_virtual_network_swift_connection.go +++ b/azurerm/internal/services/web/resource_arm_app_service_virtual_network_swift_connection.go @@ -55,7 +55,7 @@ func resourceArmAppServiceVirtualNetworkSwiftConnectionCreateUpdate(d *schema.Re ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() - appID, err := ParseAppServiceID(d.Get("app_service_id").(string)) + appID, err := parse.AppServiceID(d.Get("app_service_id").(string)) if err != nil { return fmt.Errorf("Error parsing App Service Resource ID %q", appID) } diff --git a/azurerm/internal/services/web/resource_arm_function_app.go b/azurerm/internal/services/web/resource_arm_function_app.go index b84c63c06ea4..95e2230e19f6 100644 --- a/azurerm/internal/services/web/resource_arm_function_app.go +++ b/azurerm/internal/services/web/resource_arm_function_app.go @@ -1,7 +1,6 @@ package web import ( - "context" "fmt" "log" "strings" @@ -13,10 +12,9 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/storage" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/web/parse" webValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/web/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" @@ -33,7 +31,7 @@ func resourceArmFunctionApp() *schema.Resource { Update: resourceArmFunctionAppUpdate, Delete: resourceArmFunctionAppDelete, Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { - _, err := ParseAppServiceID(id) + _, err := parse.AppServiceID(id) return err }), @@ -56,59 +54,11 @@ func resourceArmFunctionApp() *schema.Resource { "location": azure.SchemaLocation(), - "kind": { - Type: schema.TypeString, - Computed: true, - }, - "app_service_plan_id": { Type: schema.TypeString, Required: true, }, - "enabled": { - Type: schema.TypeBool, - Optional: true, - Default: true, - }, - - "version": { - Type: schema.TypeString, - Optional: true, - Default: "~1", - }, - - // TODO remove this in 3.0 - "storage_connection_string": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - Sensitive: true, - Deprecated: "Deprecated in favor of `storage_account_name` and `storage_account_access_key`", - ConflictsWith: []string{"storage_account_name", "storage_account_access_key"}, - }, - - "storage_account_name": { - Type: schema.TypeString, - // Required: true, // Uncomment this in 3.0 - Optional: true, - Computed: true, // Remove this in 3.0 - ForceNew: true, - ValidateFunc: storage.ValidateArmStorageAccountName, - ConflictsWith: []string{"storage_connection_string"}, - }, - - "storage_account_access_key": { - Type: schema.TypeString, - Optional: true, - Computed: true, // Remove this in 3.0 - // Required: true, // Uncomment this in 3.0 - Sensitive: true, - ValidateFunc: validation.NoZeroValues, - ConflictsWith: []string{"storage_connection_string"}, - }, - "app_settings": { Type: schema.TypeMap, Optional: true, @@ -117,11 +67,7 @@ func resourceArmFunctionApp() *schema.Resource { }, }, - "enable_builtin_logging": { - Type: schema.TypeBool, - Optional: true, - Default: true, - }, + "auth_settings": schemaAppServiceAuthSettings(), "connection_string": { Type: schema.TypeSet, @@ -133,11 +79,7 @@ func resourceArmFunctionApp() *schema.Resource { Type: schema.TypeString, Required: true, }, - "value": { - Type: schema.TypeString, - Required: true, - Sensitive: true, - }, + "type": { Type: schema.TypeString, Required: true, @@ -156,19 +98,47 @@ func resourceArmFunctionApp() *schema.Resource { }, true), DiffSuppressFunc: suppress.CaseDifference, }, + + "value": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + }, }, }, }, - "identity": azure.SchemaAppServiceIdentity(), + "client_affinity_enabled": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + + "daily_memory_time_quota": { + Type: schema.TypeInt, + Optional: true, + }, - "tags": tags.Schema(), + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, - "default_hostname": { - Type: schema.TypeString, - Computed: true, + "enable_builtin_logging": { + Type: schema.TypeBool, + Optional: true, + Default: true, }, + "https_only": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "identity": schemaAppServiceIdentity(), + "os_type": { Type: schema.TypeString, Optional: true, @@ -178,116 +148,70 @@ func resourceArmFunctionApp() *schema.Resource { }, false), }, - "outbound_ip_addresses": { - Type: schema.TypeString, - Computed: true, + "site_config": schemaAppServiceFunctionAppSiteConfig(), + + "source_control": schemaAppServiceSiteSourceControl(), + + "storage_account_name": { + Type: schema.TypeString, + // Required: true, // Uncomment this in 3.0 + Optional: true, + Computed: true, // Remove this in 3.0 + ForceNew: true, + ValidateFunc: storage.ValidateArmStorageAccountName, + ConflictsWith: []string{"storage_connection_string"}, }, - "possible_outbound_ip_addresses": { + "storage_account_access_key": { Type: schema.TypeString, - Computed: true, + Optional: true, + Computed: true, // Remove this in 3.0 + // Required: true, // Uncomment this in 3.0 + Sensitive: true, + ValidateFunc: validation.NoZeroValues, + ConflictsWith: []string{"storage_connection_string"}, }, - "client_affinity_enabled": { - Type: schema.TypeBool, - Optional: true, - Computed: true, + // TODO remove this in 3.0 + "storage_connection_string": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + Sensitive: true, + Deprecated: "Deprecated in favor of `storage_account_name` and `storage_account_access_key`", + ConflictsWith: []string{"storage_account_name", "storage_account_access_key"}, }, - "https_only": { - Type: schema.TypeBool, + "version": { + Type: schema.TypeString, Optional: true, - Default: false, + Default: "~1", }, - "daily_memory_time_quota": { - Type: schema.TypeInt, - Optional: true, + "tags": tags.Schema(), + + // Computed Only + + "default_hostname": { + Type: schema.TypeString, + Computed: true, }, - "site_config": { - Type: schema.TypeList, - Optional: true, + "kind": { + Type: schema.TypeString, Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "always_on": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "use_32_bit_worker_process": { - Type: schema.TypeBool, - Optional: true, - Default: true, - }, - "websockets_enabled": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "linux_fx_version": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "http2_enabled": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "ip_restriction": { - Type: schema.TypeList, - Optional: true, - Computed: true, - ConfigMode: schema.SchemaConfigModeAttr, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "ip_address": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validate.CIDR, - }, - "subnet_id": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - }, - }, - }, - "min_tls_version": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.OneFullStopZero), - string(web.OneFullStopOne), - string(web.OneFullStopTwo), - }, false), - }, - "ftps_state": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.AllAllowed), - string(web.Disabled), - string(web.FtpsOnly), - }, false), - }, - "pre_warmed_instance_count": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(0, 10), - }, - "cors": azure.SchemaWebCorsSettings(), - }, - }, }, - "auth_settings": azure.SchemaAppServiceAuthSettings(), + "outbound_ip_addresses": { + Type: schema.TypeString, + Computed: true, + }, + + "possible_outbound_ip_addresses": { + Type: schema.TypeString, + Computed: true, + }, "site_credential": { Type: schema.TypeList, @@ -321,17 +245,15 @@ func resourceArmFunctionAppCreate(d *schema.ResourceData, meta interface{}) erro name := d.Get("name").(string) resourceGroup := d.Get("resource_group_name").(string) - if features.ShouldResourcesBeImported() { - existing, err := client.Get(ctx, resourceGroup, name) - if err != nil { - if !utils.ResponseWasNotFound(existing.Response) { - return fmt.Errorf("Error checking for presence of existing Function App %q (Resource Group %q): %s", name, resourceGroup, err) - } + existing, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of existing Function App %q (Resource Group %q): %s", name, resourceGroup, err) } + } - if existing.ID != nil && *existing.ID != "" { - return tf.ImportAsExistsError("azurerm_function_app", *existing.ID) - } + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_function_app", *existing.ID) } availabilityRequest := web.ResourceNameAvailabilityRequest{ @@ -395,7 +317,7 @@ func resourceArmFunctionAppCreate(d *schema.ResourceData, meta interface{}) erro if _, ok := d.GetOk("identity"); ok { appServiceIdentityRaw := d.Get("identity").([]interface{}) - appServiceIdentity := azure.ExpandAppServiceIdentity(appServiceIdentityRaw) + appServiceIdentity := expandAppServiceIdentity(appServiceIdentityRaw) siteEnvelope.Identity = appServiceIdentity } @@ -409,18 +331,33 @@ func resourceArmFunctionAppCreate(d *schema.ResourceData, meta interface{}) erro return err } + if _, ok := d.GetOk("source_control"); ok { + if siteConfig.ScmType != "" { + return fmt.Errorf("cannot set source_control parameters when scm_type is set to %q", siteConfig.ScmType) + } + sourceControlProperties := expandAppServiceSiteSourceControl(d) + sourceControl := &web.SiteSourceControl{} + sourceControl.SiteSourceControlProperties = sourceControlProperties + // TODO - Do we need to lock the function app for updates? + _, err := client.CreateOrUpdateSourceControl(ctx, resourceGroup, name, *sourceControl) + if err != nil { + return fmt.Errorf("failed to create App Service Source Control for %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + read, err := client.Get(ctx, resourceGroup, name) if err != nil { return err } - if read.ID == nil { + + if read.ID == nil || *read.ID == "" { return fmt.Errorf("Cannot read Function App %s (resource group %s) ID", name, resourceGroup) } d.SetId(*read.ID) authSettingsRaw := d.Get("auth_settings").([]interface{}) - authSettings := azure.ExpandAppServiceAuthSettings(authSettingsRaw) + authSettings := expandAppServiceAuthSettings(authSettingsRaw) auth := web.SiteAuthSettings{ ID: read.ID, @@ -439,7 +376,7 @@ func resourceArmFunctionAppUpdate(d *schema.ResourceData, meta interface{}) erro ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := ParseAppServiceID(d.Id()) + id, err := parse.AppServiceID(d.Id()) if err != nil { return err } @@ -492,7 +429,7 @@ func resourceArmFunctionAppUpdate(d *schema.ResourceData, meta interface{}) erro if _, ok := d.GetOk("identity"); ok { appServiceIdentityRaw := d.Get("identity").([]interface{}) - appServiceIdentity := azure.ExpandAppServiceIdentity(appServiceIdentityRaw) + appServiceIdentity := expandAppServiceIdentity(appServiceIdentityRaw) siteEnvelope.Identity = appServiceIdentity } @@ -517,7 +454,11 @@ func resourceArmFunctionAppUpdate(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("Error updating Application Settings for Function App %q: %+v", id.Name, err) } - if d.HasChange("site_config") { + // If `source_control` is defined, we need to set site_config.0.scm_type to "None" or we cannot update it + // repo_url is required by the API + _, hasSourceControl := d.GetOk("source_control.0.repo_url") + + if d.HasChange("site_config") || hasSourceControl { siteConfig, err := expandFunctionAppSiteConfig(d) if err != nil { return fmt.Errorf("Error expanding `site_config` for Function App %q (Resource Group %q): %s", id.Name, id.ResourceGroup, err) @@ -525,14 +466,39 @@ func resourceArmFunctionAppUpdate(d *schema.ResourceData, meta interface{}) erro siteConfigResource := web.SiteConfigResource{ SiteConfig: &siteConfig, } + + if hasSourceControl { + siteConfigResource.SiteConfig.ScmType = "None" + } + if _, err := client.CreateOrUpdateConfiguration(ctx, id.ResourceGroup, id.Name, siteConfigResource); err != nil { return fmt.Errorf("Error updating Configuration for Function App %q: %+v", id.Name, err) } } + if hasSourceControl { + sourceControlProperties := expandAppServiceSiteSourceControl(d) + sourceControl := &web.SiteSourceControl{} + sourceControl.SiteSourceControlProperties = sourceControlProperties + scFuture, err := client.CreateOrUpdateSourceControl(ctx, id.ResourceGroup, id.Name, *sourceControl) + if err != nil { + return fmt.Errorf("failed to create App Service Source Control for %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + + err = scFuture.WaitForCompletionRef(ctx, client.Client) + if err != nil { + return fmt.Errorf("failed waiting for App Service Source Control configuration: %+v", err) + } + + sc, err := client.GetSourceControl(ctx, id.ResourceGroup, id.Name) + if err != nil { + return fmt.Errorf("failed reading back App Service Source Control for %q", *sc.Name) + } + } + if d.HasChange("auth_settings") { authSettingsRaw := d.Get("auth_settings").([]interface{}) - authSettingsProperties := azure.ExpandAppServiceAuthSettings(authSettingsRaw) + authSettingsProperties := expandAppServiceAuthSettings(authSettingsRaw) authSettings := web.SiteAuthSettings{ ID: utils.String(d.Id()), SiteAuthSettingsProperties: &authSettingsProperties, @@ -563,7 +529,7 @@ func resourceArmFunctionAppRead(d *schema.ResourceData, meta interface{}) error ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := ParseAppServiceID(d.Id()) + id, err := parse.AppServiceID(d.Id()) if err != nil { return err } @@ -686,7 +652,7 @@ func resourceArmFunctionAppRead(d *schema.ResourceData, meta interface{}) error return err } - identity := azure.FlattenAppServiceIdentity(resp.Identity) + identity := flattenAppServiceIdentity(resp.Identity) if err := d.Set("identity", identity); err != nil { return fmt.Errorf("Error setting `identity`: %s", err) } @@ -701,11 +667,20 @@ func resourceArmFunctionAppRead(d *schema.ResourceData, meta interface{}) error return err } - authSettings := azure.FlattenAppServiceAuthSettings(authResp.SiteAuthSettingsProperties) + authSettings := flattenAppServiceAuthSettings(authResp.SiteAuthSettingsProperties) if err := d.Set("auth_settings", authSettings); err != nil { return fmt.Errorf("Error setting `auth_settings`: %s", err) } + scmResp, err := client.GetSourceControl(ctx, id.ResourceGroup, id.Name) + if err != nil { + return fmt.Errorf("Error making Read request on Function App Source Control %q: %+v", id.Name, err) + } + scm := flattenAppServiceSourceControl(scmResp.SiteSourceControlProperties) + if err := d.Set("source_control", scm); err != nil { + return fmt.Errorf("Error setting `source_control`: %s", err) + } + siteCred := flattenFunctionAppSiteCredential(siteCredResp.UserProperties) if err = d.Set("site_credential", siteCred); err != nil { return err @@ -719,7 +694,7 @@ func resourceArmFunctionAppDelete(d *schema.ResourceData, meta interface{}) erro ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := ParseAppServiceID(d.Id()) + id, err := parse.AppServiceID(d.Id()) if err != nil { return err } @@ -737,335 +712,3 @@ func resourceArmFunctionAppDelete(d *schema.ResourceData, meta interface{}) erro return nil } - -func getBasicFunctionAppAppSettings(d *schema.ResourceData, appServiceTier, endpointSuffix string) ([]web.NameValuePair, error) { - // TODO: This is a workaround since there are no public Functions API - // You may track the API request here: https://github.com/Azure/azure-rest-api-specs/issues/3750 - dashboardPropName := "AzureWebJobsDashboard" - storagePropName := "AzureWebJobsStorage" - functionVersionPropName := "FUNCTIONS_EXTENSION_VERSION" - - contentSharePropName := "WEBSITE_CONTENTSHARE" - contentFileConnStringPropName := "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING" - - // TODO 3.0 - remove this logic for determining which storage account connection string to use - storageConnection := "" - if v, ok := d.GetOk("storage_connection_string"); ok { - storageConnection = v.(string) - } - - storageAccount := "" - if v, ok := d.GetOk("storage_account_name"); ok { - storageAccount = v.(string) - } - - connectionString := "" - if v, ok := d.GetOk("storage_account_access_key"); ok { - connectionString = v.(string) - } - - if storageConnection == "" && storageAccount == "" && connectionString == "" { - return nil, fmt.Errorf("one of `storage_connection_string` or `storage_account_name` and `storage_account_access_key` must be specified") - } - - if (storageAccount == "" && connectionString != "") || (storageAccount != "" && connectionString == "") { - return nil, fmt.Errorf("both `storage_account_name` and `storage_account_access_key` must be specified") - } - - if connectionString != "" && storageAccount != "" { - storageConnection = fmt.Sprintf("DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s;EndpointSuffix=%s", storageAccount, connectionString, endpointSuffix) - } - - functionVersion := d.Get("version").(string) - contentShare := strings.ToLower(d.Get("name").(string)) + "-content" - - basicSettings := []web.NameValuePair{ - {Name: &storagePropName, Value: &storageConnection}, - {Name: &functionVersionPropName, Value: &functionVersion}, - } - - if d.Get("enable_builtin_logging").(bool) { - basicSettings = append(basicSettings, web.NameValuePair{ - Name: &dashboardPropName, - Value: &storageConnection, - }) - } - - consumptionSettings := []web.NameValuePair{ - {Name: &contentSharePropName, Value: &contentShare}, - {Name: &contentFileConnStringPropName, Value: &storageConnection}, - } - - // On consumption and premium plans include WEBSITE_CONTENT components, unless it's a Linux consumption plan - // (see https://github.com/Azure/azure-functions-python-worker/issues/598) - if (strings.EqualFold(appServiceTier, "dynamic") || strings.EqualFold(appServiceTier, "elasticpremium")) && - !strings.EqualFold(d.Get("os_type").(string), "linux") { - return append(basicSettings, consumptionSettings...), nil - } - - return basicSettings, nil -} - -func getFunctionAppServiceTier(ctx context.Context, appServicePlanId string, meta interface{}) (string, error) { - id, err := ParseAppServicePlanID(appServicePlanId) - if err != nil { - return "", fmt.Errorf("[ERROR] Unable to parse App Service Plan ID %q: %+v", appServicePlanId, err) - } - - log.Printf("[DEBUG] Retrieving App Service Plan %q (Resource Group %q)", id.Name, id.ResourceGroup) - - appServicePlansClient := meta.(*clients.Client).Web.AppServicePlansClient - appServicePlan, err := appServicePlansClient.Get(ctx, id.ResourceGroup, id.Name) - if err != nil { - return "", fmt.Errorf("[ERROR] Could not retrieve App Service Plan ID %q: %+v", appServicePlanId, err) - } - - if sku := appServicePlan.Sku; sku != nil { - if tier := sku.Tier; tier != nil { - return *tier, nil - } - } - return "", fmt.Errorf("No `sku` block was returned for App Service Plan ID %q", appServicePlanId) -} - -func expandFunctionAppAppSettings(d *schema.ResourceData, appServiceTier, endpointSuffix string) (map[string]*string, error) { - output := expandAppServiceAppSettings(d) - - basicAppSettings, err := getBasicFunctionAppAppSettings(d, appServiceTier, endpointSuffix) - if err != nil { - return nil, err - } - for _, p := range basicAppSettings { - output[*p.Name] = p.Value - } - - return output, nil -} - -func expandFunctionAppSiteConfig(d *schema.ResourceData) (web.SiteConfig, error) { - configs := d.Get("site_config").([]interface{}) - siteConfig := web.SiteConfig{} - - if len(configs) == 0 { - return siteConfig, nil - } - - config := configs[0].(map[string]interface{}) - - if v, ok := config["always_on"]; ok { - siteConfig.AlwaysOn = utils.Bool(v.(bool)) - } - - if v, ok := config["use_32_bit_worker_process"]; ok { - siteConfig.Use32BitWorkerProcess = utils.Bool(v.(bool)) - } - - if v, ok := config["websockets_enabled"]; ok { - siteConfig.WebSocketsEnabled = utils.Bool(v.(bool)) - } - - if v, ok := config["linux_fx_version"]; ok { - siteConfig.LinuxFxVersion = utils.String(v.(string)) - } - - if v, ok := config["cors"]; ok { - corsSettings := v.(interface{}) - expand := azure.ExpandWebCorsSettings(corsSettings) - siteConfig.Cors = &expand - } - - if v, ok := config["http2_enabled"]; ok { - siteConfig.HTTP20Enabled = utils.Bool(v.(bool)) - } - - if v, ok := config["ip_restriction"]; ok { - ipSecurityRestrictions := v.(interface{}) - restrictions, err := expandFunctionAppIpRestriction(ipSecurityRestrictions) - if err != nil { - return siteConfig, err - } - siteConfig.IPSecurityRestrictions = &restrictions - } - - if v, ok := config["min_tls_version"]; ok { - siteConfig.MinTLSVersion = web.SupportedTLSVersions(v.(string)) - } - - if v, ok := config["ftps_state"]; ok { - siteConfig.FtpsState = web.FtpsState(v.(string)) - } - - if v, ok := config["pre_warmed_instance_count"]; ok { - siteConfig.PreWarmedInstanceCount = utils.Int32(int32(v.(int))) - } - - return siteConfig, nil -} - -func flattenFunctionAppSiteConfig(input *web.SiteConfig) []interface{} { - results := make([]interface{}, 0) - result := make(map[string]interface{}) - - if input == nil { - log.Printf("[DEBUG] SiteConfig is nil") - return results - } - - if input.AlwaysOn != nil { - result["always_on"] = *input.AlwaysOn - } - - if input.Use32BitWorkerProcess != nil { - result["use_32_bit_worker_process"] = *input.Use32BitWorkerProcess - } - - if input.WebSocketsEnabled != nil { - result["websockets_enabled"] = *input.WebSocketsEnabled - } - - if input.LinuxFxVersion != nil { - result["linux_fx_version"] = *input.LinuxFxVersion - } - - if input.HTTP20Enabled != nil { - result["http2_enabled"] = *input.HTTP20Enabled - } - - if input.PreWarmedInstanceCount != nil { - result["pre_warmed_instance_count"] = *input.PreWarmedInstanceCount - } - - result["ip_restriction"] = flattenFunctionAppIpRestriction(input.IPSecurityRestrictions) - - result["min_tls_version"] = string(input.MinTLSVersion) - result["ftps_state"] = string(input.FtpsState) - - result["cors"] = azure.FlattenWebCorsSettings(input.Cors) - - results = append(results, result) - return results -} - -func expandFunctionAppConnectionStrings(d *schema.ResourceData) map[string]*web.ConnStringValueTypePair { - input := d.Get("connection_string").(*schema.Set).List() - output := make(map[string]*web.ConnStringValueTypePair, len(input)) - - for _, v := range input { - vals := v.(map[string]interface{}) - - csName := vals["name"].(string) - csType := vals["type"].(string) - csValue := vals["value"].(string) - - output[csName] = &web.ConnStringValueTypePair{ - Value: utils.String(csValue), - Type: web.ConnectionStringType(csType), - } - } - - return output -} - -func expandFunctionAppIpRestriction(input interface{}) ([]web.IPSecurityRestriction, error) { - restrictions := make([]web.IPSecurityRestriction, 0) - - for i, r := range input.([]interface{}) { - if r == nil { - continue - } - - restriction := r.(map[string]interface{}) - - ipAddress := restriction["ip_address"].(string) - vNetSubnetID := restriction["subnet_id"].(string) - - if vNetSubnetID != "" && ipAddress != "" { - return nil, fmt.Errorf(fmt.Sprintf("only one of `ip_address` or `subnet_id` can set for `site_config.0.ip_restriction.%d`", i)) - } - - if vNetSubnetID == "" && ipAddress == "" { - return nil, fmt.Errorf(fmt.Sprintf("one of `ip_address` or `subnet_id` must be set for `site_config.0.ip_restriction.%d`", i)) - } - - ipSecurityRestriction := web.IPSecurityRestriction{} - if ipAddress == "Any" { - continue - } - - if ipAddress != "" { - ipSecurityRestriction.IPAddress = &ipAddress - } - - if vNetSubnetID != "" { - ipSecurityRestriction.VnetSubnetResourceID = &vNetSubnetID - } - - restrictions = append(restrictions, ipSecurityRestriction) - } - - return restrictions, nil -} - -func flattenFunctionAppConnectionStrings(input map[string]*web.ConnStringValueTypePair) interface{} { - results := make([]interface{}, 0) - - for k, v := range input { - result := make(map[string]interface{}) - result["name"] = k - result["type"] = string(v.Type) - result["value"] = *v.Value - results = append(results, result) - } - - return results -} - -func flattenFunctionAppSiteCredential(input *web.UserProperties) []interface{} { - results := make([]interface{}, 0) - result := make(map[string]interface{}) - - if input == nil { - log.Printf("[DEBUG] UserProperties is nil") - return results - } - - if input.PublishingUserName != nil { - result["username"] = *input.PublishingUserName - } - - if input.PublishingPassword != nil { - result["password"] = *input.PublishingPassword - } - - return append(results, result) -} - -func flattenFunctionAppIpRestriction(input *[]web.IPSecurityRestriction) []interface{} { - restrictions := make([]interface{}, 0) - - if input == nil { - return restrictions - } - - for _, v := range *input { - ipAddress := "" - if v.IPAddress != nil { - ipAddress = *v.IPAddress - if ipAddress == "Any" { - continue - } - } - - subnetId := "" - if v.VnetSubnetResourceID != nil { - subnetId = *v.VnetSubnetResourceID - } - - restrictions = append(restrictions, map[string]interface{}{ - "ip_address": ipAddress, - "subnet_id": subnetId, - }) - } - - return restrictions -} diff --git a/azurerm/internal/services/web/resource_arm_function_app_slot.go b/azurerm/internal/services/web/resource_arm_function_app_slot.go index b7e1b7d71adf..b5a72c3f11ee 100644 --- a/azurerm/internal/services/web/resource_arm_function_app_slot.go +++ b/azurerm/internal/services/web/resource_arm_function_app_slot.go @@ -13,7 +13,6 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/storage" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/web/parse" @@ -53,7 +52,7 @@ func resourceArmFunctionAppSlot() *schema.Resource { "location": azure.SchemaLocation(), - "identity": azure.SchemaAppServiceIdentity(), + "identity": schemaAppServiceIdentity(), "function_app_name": { Type: schema.TypeString, @@ -192,93 +191,9 @@ func resourceArmFunctionAppSlot() *schema.Resource { Computed: true, }, - "site_config": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "always_on": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "use_32_bit_worker_process": { - Type: schema.TypeBool, - Optional: true, - Default: true, - }, - "websockets_enabled": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "linux_fx_version": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "http2_enabled": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "ip_restriction": { - Type: schema.TypeList, - Optional: true, - Computed: true, - ConfigMode: schema.SchemaConfigModeAttr, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "ip_address": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validate.CIDR, - }, - "subnet_id": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - }, - }, - }, - "min_tls_version": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.OneFullStopZero), - string(web.OneFullStopOne), - string(web.OneFullStopTwo), - }, false), - }, - "ftps_state": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.AllAllowed), - string(web.Disabled), - string(web.FtpsOnly), - }, false), - }, - "pre_warmed_instance_count": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(0, 10), - }, - "cors": azure.SchemaWebCorsSettings(), - "auto_swap_slot_name": { - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, + "site_config": schemaAppServiceFunctionAppSiteConfig(), - "auth_settings": azure.SchemaAppServiceAuthSettings(), + "auth_settings": schemaAppServiceAuthSettings(), "site_credential": { Type: schema.TypeList, @@ -350,7 +265,7 @@ func resourceArmFunctionAppSlotCreate(d *schema.ResourceData, meta interface{}) basicAppSettings := getBasicFunctionAppSlotAppSettings(d, appServiceTier, endpointSuffix) - siteConfig, err := expandFunctionAppSlotSiteConfig(d) + siteConfig, err := expandFunctionAppSiteConfig(d) if err != nil { return fmt.Errorf("Error expanding `site_config` for Function App Slot %q (Resource Group %q): %s", slot, resourceGroup, err) } @@ -373,7 +288,7 @@ func resourceArmFunctionAppSlotCreate(d *schema.ResourceData, meta interface{}) if _, ok := d.GetOk("identity"); ok { appServiceIdentityRaw := d.Get("identity").([]interface{}) - appServiceIdentity := azure.ExpandAppServiceIdentity(appServiceIdentityRaw) + appServiceIdentity := expandAppServiceIdentity(appServiceIdentityRaw) siteEnvelope.Identity = appServiceIdentity } @@ -398,7 +313,7 @@ func resourceArmFunctionAppSlotCreate(d *schema.ResourceData, meta interface{}) d.SetId(*read.ID) authSettingsRaw := d.Get("auth_settings").([]interface{}) - authSettings := azure.ExpandAppServiceAuthSettings(authSettingsRaw) + authSettings := expandAppServiceAuthSettings(authSettingsRaw) auth := web.SiteAuthSettings{ ID: read.ID, @@ -444,7 +359,7 @@ func resourceArmFunctionAppSlotUpdate(d *schema.ResourceData, meta interface{}) basicAppSettings := getBasicFunctionAppSlotAppSettings(d, appServiceTier, endpointSuffix) - siteConfig, err := expandFunctionAppSlotSiteConfig(d) + siteConfig, err := expandFunctionAppSiteConfig(d) if err != nil { return fmt.Errorf("Error expanding `site_config` for Slot %q (Function App %q / Resource Group %q): %s", id.Name, id.FunctionAppName, id.ResourceGroup, err) } @@ -467,7 +382,7 @@ func resourceArmFunctionAppSlotUpdate(d *schema.ResourceData, meta interface{}) if _, ok := d.GetOk("identity"); ok { appServiceIdentityRaw := d.Get("identity").([]interface{}) - appServiceIdentity := azure.ExpandAppServiceIdentity(appServiceIdentityRaw) + appServiceIdentity := expandAppServiceIdentity(appServiceIdentityRaw) siteEnvelope.Identity = appServiceIdentity } @@ -494,7 +409,7 @@ func resourceArmFunctionAppSlotUpdate(d *schema.ResourceData, meta interface{}) } if d.HasChange("site_config") { - siteConfig, err := expandFunctionAppSlotSiteConfig(d) + siteConfig, err := expandFunctionAppSiteConfig(d) if err != nil { return fmt.Errorf("Error expanding `site_config` for Slot %q (Function App %q / Resource Group %q): %s", id.Name, id.FunctionAppName, id.ResourceGroup, err) } @@ -508,7 +423,7 @@ func resourceArmFunctionAppSlotUpdate(d *schema.ResourceData, meta interface{}) if d.HasChange("auth_settings") { authSettingsRaw := d.Get("auth_settings").([]interface{}) - authSettingsProperties := azure.ExpandAppServiceAuthSettings(authSettingsRaw) + authSettingsProperties := expandAppServiceAuthSettings(authSettingsRaw) authSettings := web.SiteAuthSettings{ ID: utils.String(d.Id()), SiteAuthSettingsProperties: &authSettingsProperties, @@ -650,7 +565,7 @@ func resourceArmFunctionAppSlotRead(d *schema.ResourceData, meta interface{}) er return err } - identity := azure.FlattenAppServiceIdentity(resp.Identity) + identity := flattenAppServiceIdentity(resp.Identity) if err := d.Set("identity", identity); err != nil { return fmt.Errorf("Error setting `identity`: %s", err) } @@ -660,12 +575,12 @@ func resourceArmFunctionAppSlotRead(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("Error making Read request on AzureRM Function App Configuration %q: %+v", id.Name, err) } - siteConfig := flattenFunctionAppSlotSiteConfig(configResp.SiteConfig) + siteConfig := flattenFunctionAppSiteConfig(configResp.SiteConfig) if err = d.Set("site_config", siteConfig); err != nil { return err } - authSettings := azure.FlattenAppServiceAuthSettings(authResp.SiteAuthSettingsProperties) + authSettings := flattenAppServiceAuthSettings(authResp.SiteAuthSettingsProperties) if err := d.Set("auth_settings", authSettings); err != nil { return fmt.Errorf("Error setting `auth_settings`: %s", err) } @@ -779,118 +694,6 @@ func expandFunctionAppSlotAppSettings(d *schema.ResourceData, appServiceTier, en return output, nil } -func expandFunctionAppSlotSiteConfig(d *schema.ResourceData) (web.SiteConfig, error) { - configs := d.Get("site_config").([]interface{}) - siteConfig := web.SiteConfig{} - - if len(configs) == 0 { - return siteConfig, nil - } - - config := configs[0].(map[string]interface{}) - - if v, ok := config["always_on"]; ok { - siteConfig.AlwaysOn = utils.Bool(v.(bool)) - } - - if v, ok := config["use_32_bit_worker_process"]; ok { - siteConfig.Use32BitWorkerProcess = utils.Bool(v.(bool)) - } - - if v, ok := config["websockets_enabled"]; ok { - siteConfig.WebSocketsEnabled = utils.Bool(v.(bool)) - } - - if v, ok := config["linux_fx_version"]; ok { - siteConfig.LinuxFxVersion = utils.String(v.(string)) - } - - if v, ok := config["cors"]; ok { - corsSettings := v.(interface{}) - expand := azure.ExpandWebCorsSettings(corsSettings) - siteConfig.Cors = &expand - } - - if v, ok := config["http2_enabled"]; ok { - siteConfig.HTTP20Enabled = utils.Bool(v.(bool)) - } - - if v, ok := config["ip_restriction"]; ok { - ipSecurityRestrictions := v.(interface{}) - restrictions, err := expandFunctionAppSlotIPRestriction(ipSecurityRestrictions) - if err != nil { - return siteConfig, err - } - siteConfig.IPSecurityRestrictions = &restrictions - } - - if v, ok := config["min_tls_version"]; ok { - siteConfig.MinTLSVersion = web.SupportedTLSVersions(v.(string)) - } - - if v, ok := config["ftps_state"]; ok { - siteConfig.FtpsState = web.FtpsState(v.(string)) - } - - if v, ok := config["pre_warmed_instance_count"]; ok { - siteConfig.PreWarmedInstanceCount = utils.Int32(int32(v.(int))) - } - - if v, ok := config["auto_swap_slot_name"]; ok { - siteConfig.AutoSwapSlotName = utils.String(v.(string)) - } - - return siteConfig, nil -} - -func flattenFunctionAppSlotSiteConfig(input *web.SiteConfig) []interface{} { - results := make([]interface{}, 0) - result := make(map[string]interface{}) - - if input == nil { - log.Printf("[DEBUG] SiteConfig is nil") - return results - } - - if input.AlwaysOn != nil { - result["always_on"] = *input.AlwaysOn - } - - if input.Use32BitWorkerProcess != nil { - result["use_32_bit_worker_process"] = *input.Use32BitWorkerProcess - } - - if input.WebSocketsEnabled != nil { - result["websockets_enabled"] = *input.WebSocketsEnabled - } - - if input.LinuxFxVersion != nil { - result["linux_fx_version"] = *input.LinuxFxVersion - } - - if input.HTTP20Enabled != nil { - result["http2_enabled"] = *input.HTTP20Enabled - } - - if input.PreWarmedInstanceCount != nil { - result["pre_warmed_instance_count"] = *input.PreWarmedInstanceCount - } - - result["ip_restriction"] = flattenFunctionAppSlotIPRestriction(input.IPSecurityRestrictions) - - result["min_tls_version"] = string(input.MinTLSVersion) - result["ftps_state"] = string(input.FtpsState) - - result["cors"] = azure.FlattenWebCorsSettings(input.Cors) - - if input.AutoSwapSlotName != nil { - result["auto_swap_slot_name"] = *input.AutoSwapSlotName - } - - results = append(results, result) - return results -} - func expandFunctionAppSlotConnectionStrings(d *schema.ResourceData) map[string]*web.ConnStringValueTypePair { input := d.Get("connection_string").(*schema.Set).List() output := make(map[string]*web.ConnStringValueTypePair, len(input)) @@ -911,46 +714,6 @@ func expandFunctionAppSlotConnectionStrings(d *schema.ResourceData) map[string]* return output } -func expandFunctionAppSlotIPRestriction(input interface{}) ([]web.IPSecurityRestriction, error) { - restrictions := make([]web.IPSecurityRestriction, 0) - - for i, r := range input.([]interface{}) { - if r == nil { - continue - } - - restriction := r.(map[string]interface{}) - - ipAddress := restriction["ip_address"].(string) - vNetSubnetID := restriction["subnet_id"].(string) - - if vNetSubnetID != "" && ipAddress != "" { - return nil, fmt.Errorf(fmt.Sprintf("only one of `ip_address` or `subnet_id` can set for `site_config.0.ip_restriction.%d`", i)) - } - - if vNetSubnetID == "" && ipAddress == "" { - return nil, fmt.Errorf(fmt.Sprintf("one of `ip_address` or `subnet_id` must be set for `site_config.0.ip_restriction.%d`", i)) - } - - ipSecurityRestriction := web.IPSecurityRestriction{} - if ipAddress == "Any" { - continue - } - - if ipAddress != "" { - ipSecurityRestriction.IPAddress = &ipAddress - } - - if vNetSubnetID != "" { - ipSecurityRestriction.VnetSubnetResourceID = &vNetSubnetID - } - - restrictions = append(restrictions, ipSecurityRestriction) - } - - return restrictions, nil -} - func flattenFunctionAppSlotConnectionStrings(input map[string]*web.ConnStringValueTypePair) interface{} { results := make([]interface{}, 0) @@ -984,33 +747,3 @@ func flattenFunctionAppSlotSiteCredential(input *web.UserProperties) []interface return append(results, result) } - -func flattenFunctionAppSlotIPRestriction(input *[]web.IPSecurityRestriction) []interface{} { - restrictions := make([]interface{}, 0) - - if input == nil { - return restrictions - } - - for _, v := range *input { - ipAddress := "" - if v.IPAddress != nil { - ipAddress = *v.IPAddress - if ipAddress == "Any" { - continue - } - } - - subnetID := "" - if v.VnetSubnetResourceID != nil { - subnetID = *v.VnetSubnetResourceID - } - - restrictions = append(restrictions, map[string]interface{}{ - "ip_address": ipAddress, - "subnet_id": subnetID, - }) - } - - return restrictions -} diff --git a/azurerm/internal/services/web/tests/data_source_app_service_test.go b/azurerm/internal/services/web/tests/data_source_app_service_test.go index 2981ad628727..b1caf73e166c 100644 --- a/azurerm/internal/services/web/tests/data_source_app_service_test.go +++ b/azurerm/internal/services/web/tests/data_source_app_service_test.go @@ -185,6 +185,24 @@ func TestAccDataSourceAzureRMAppService_scmIPRestriction(t *testing.T) { }) } +func TestAccDataSourceAzureRMAppService_withSourceControl(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_app_service", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMAppServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAppService_withSourceControl(data, "main"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(data.ResourceName, "source_control.0.branch", "main"), + ), + }, + }, + }) +} + func TestAccDataSourceAzureRMAppService_http2Enabled(t *testing.T) { data := acceptance.BuildTestData(t, "data.azurerm_app_service", "test") @@ -348,6 +366,18 @@ data "azurerm_app_service" "test" { `, config) } +func testAccDataSourceAppService_withSourceControl(data acceptance.TestData, branch string) string { + config := testAccAzureRMAppService_withSourceControl(data, branch) + return fmt.Sprintf(` +%s + +data "azurerm_app_service" "test" { + name = azurerm_app_service.test.name + resource_group_name = azurerm_app_service.test.resource_group_name +} +`, config) +} + func testAccDataSourceAppService_http2Enabled(data acceptance.TestData) string { config := testAccAzureRMAppService_http2Enabled(data) return fmt.Sprintf(` diff --git a/azurerm/internal/services/web/tests/data_source_function_app_test.go b/azurerm/internal/services/web/tests/data_source_function_app_test.go index 7225a4985db0..697a43f2aca1 100644 --- a/azurerm/internal/services/web/tests/data_source_function_app_test.go +++ b/azurerm/internal/services/web/tests/data_source_function_app_test.go @@ -4,9 +4,8 @@ import ( "fmt" "testing" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" ) func TestAccDataSourceAzureRMFunctionApp_basic(t *testing.T) { @@ -70,6 +69,46 @@ func TestAccDataSourceAzureRMFunctionApp_connectionStrings(t *testing.T) { }) } +func TestAccDataSourceAzureRMFunctionApp_withSourceControl(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_function_app", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMFunctionAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAzureRMFunctionApp_withSourceControl(data, "main"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(data.ResourceName, "source_control.0.branch", "main"), + ), + }, + }, + }) +} + +func TestAccDataSourceAzureRMFunctionApp_siteConfig(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_function_app", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMFunctionAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAzureRMFunctionApp_withSiteConfig(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.0.ip_address", "10.10.10.10/32"), + resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.1.ip_address", "20.20.20.0/24"), + resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.2.ip_address", "30.30.0.0/16"), + resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.3.ip_address", "192.168.1.2/24"), + ), + }, + }, + }) +} + func testAccDataSourceAzureRMFunctionApp_basic(data acceptance.TestData) string { template := testAccAzureRMFunctionApp_basic(data) return fmt.Sprintf(` @@ -105,3 +144,27 @@ data "azurerm_function_app" "test" { } `, template) } + +func testAccDataSourceAzureRMFunctionApp_withSourceControl(data acceptance.TestData, branch string) string { + config := testAccAzureRMFunctionApp_withSourceControl(data, branch) + return fmt.Sprintf(` +%s + +data "azurerm_function_app" "test" { + name = azurerm_function_app.test.name + resource_group_name = azurerm_resource_group.test.name +} +`, config) +} + +func testAccDataSourceAzureRMFunctionApp_withSiteConfig(data acceptance.TestData) string { + config := testAccAzureRMFunctionApp_manyIpRestrictions(data) + return fmt.Sprintf(` +%s + +data "azurerm_function_app" "test" { + name = azurerm_function_app.test.name + resource_group_name = azurerm_resource_group.test.name +} +`, config) +} diff --git a/azurerm/internal/services/web/tests/resource_arm_app_service_slot_test.go b/azurerm/internal/services/web/tests/resource_arm_app_service_slot_test.go index e4e062d3106c..a7d6b900be20 100644 --- a/azurerm/internal/services/web/tests/resource_arm_app_service_slot_test.go +++ b/azurerm/internal/services/web/tests/resource_arm_app_service_slot_test.go @@ -767,12 +767,47 @@ func TestAccAzureRMAppServiceSlot_manyIpRestrictions(t *testing.T) { Config: testAccAzureRMAppServiceSlot_manyIpRestrictions(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMAppServiceSlotExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.0.ip_address", "10.10.10.10/32"), - resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.1.ip_address", "20.20.20.0/24"), - resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.2.ip_address", "30.30.0.0/16"), - resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.3.ip_address", "192.168.1.2/24"), ), }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMAppServiceSlot_scmUseMainIPRestriction(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_app_service_slot", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMAppServiceSlotDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAppServiceSlot_scmUseMainIPRestriction(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServiceSlotExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMAppServiceSlot_scmOneIPRestriction(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_app_service_slot", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMAppServiceSlotDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAppServiceSlot_scmOneIPRestriction(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServiceSlotExists(data.ResourceName), + ), + }, + data.ImportStep(), }, }) } @@ -1018,6 +1053,7 @@ func TestAccAzureRMAppServiceSlot_windowsJava8Jetty(t *testing.T) { }, }) } + func TestAccAzureRMAppServiceSlot_windowsJava11Jetty(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_app_service_slot", "test") @@ -1271,6 +1307,7 @@ func TestAccAzureRMAppServiceSlot_applicationBlobStorageLogs(t *testing.T) { }, }) } + func TestAccAzureRMAppServiceSlot_httpFileSystemLogs(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_app_service_slot", "test") @@ -1291,6 +1328,7 @@ func TestAccAzureRMAppServiceSlot_httpFileSystemLogs(t *testing.T) { }, }) } + func TestAccAzureRMAppServiceSlot_httpBlobStorageLogs(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_app_service_slot", "test") @@ -2799,6 +2837,123 @@ resource "azurerm_app_service_slot" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger) } +func testAccAzureRMAppServiceSlot_scmUseMainIPRestriction(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-web-%d" + location = "%s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctestvirtnet%d" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet" "test" { + name = "acctestsubnet%d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefix = "10.0.2.0/24" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_app_service" "test" { + name = "acctestAS-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + app_service_plan_id = azurerm_app_service_plan.test.id +} + +resource "azurerm_app_service_slot" "test" { + name = "acctestASSlot-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + app_service_plan_id = azurerm_app_service_plan.test.id + app_service_name = azurerm_app_service.test.name + + site_config { + scm_use_main_ip_restriction = true + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger) +} + +func testAccAzureRMAppServiceSlot_scmOneIPRestriction(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-web-%d" + location = "%s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctestvirtnet%d" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet" "test" { + name = "acctestsubnet%d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefix = "10.0.2.0/24" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_app_service" "test" { + name = "acctestAS-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + app_service_plan_id = azurerm_app_service_plan.test.id +} + +resource "azurerm_app_service_slot" "test" { + name = "acctestASSlot-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + app_service_plan_id = azurerm_app_service_plan.test.id + app_service_name = azurerm_app_service.test.name + + site_config { + scm_ip_restriction { + ip_address = "10.10.10.10/32" + action = "Allow" + } + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger) +} + func testAccAzureRMAppServiceSlot_zeroedIpRestriction(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/azurerm/internal/services/web/tests/resource_arm_app_service_test.go b/azurerm/internal/services/web/tests/resource_arm_app_service_test.go index c6fcd18f4561..09a70382b218 100644 --- a/azurerm/internal/services/web/tests/resource_arm_app_service_test.go +++ b/azurerm/internal/services/web/tests/resource_arm_app_service_test.go @@ -1379,6 +1379,49 @@ func TestAccAzureRMAppService_scmType(t *testing.T) { }) } +func TestAccAzureRMAppService_withSourceControl(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_app_service", "test") + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMAppServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAppService_withSourceControl(data, "main"), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServiceExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMAppService_withSourceControlUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_app_service", "test") + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMAppServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAppService_withSourceControl(data, "main"), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServiceExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMAppService_withSourceControl(data, "development"), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServiceExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + func TestAccAzureRMAppService_ftpsState(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_app_service", "test") resource.ParallelTest(t, resource.TestCase{ @@ -1417,8 +1460,7 @@ func TestAccAzureRMAppService_healthCheckPath(t *testing.T) { }) } -// todo - linuxFxVersion seems to reject all supplied values - needs more detailed investigation. -// error message simply reads: Original Error: Code="BadRequest" Message="The parameter LinuxFxVersion has an invalid value." +// Note: to specify `linux_fx_version` the App Service Plan must be of `kind = "Linux"`, and `reserved = true` func TestAccAzureRMAppService_linuxFxVersion(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_app_service", "test") resource.ParallelTest(t, resource.TestCase{ @@ -1430,11 +1472,9 @@ func TestAccAzureRMAppService_linuxFxVersion(t *testing.T) { Config: testAccAzureRMAppService_linuxFxVersion(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMAppServiceExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.always_on", "true"), - resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.linux_fx_version", "DOCKER|(golang:latest)"), - resource.TestCheckResourceAttr(data.ResourceName, "app_settings.WEBSITES_ENABLE_APP_SERVICE_STORAGE", "false"), ), }, + data.ImportStep(), }, }) } @@ -3488,6 +3528,7 @@ resource "azurerm_app_service" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) } + func testAccAzureRMAppService_applicationBlobStorageLogsWithAppSettings(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { @@ -3531,6 +3572,7 @@ resource "azurerm_app_service" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) } + func testAccAzureRMAppService_httpFileSystemLogs(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { @@ -3570,6 +3612,7 @@ resource "azurerm_app_service" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) } + func testAccAzureRMAppService_httpBlobStorageLogs(data acceptance.TestData) string { template := testAccAzureRMAppService_backupTemplate(data) return fmt.Sprintf(` @@ -3981,6 +4024,44 @@ resource "azurerm_app_service" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) } +func testAccAzureRMAppService_withSourceControl(data acceptance.TestData, branch string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-web-%d" + location = "%s" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_app_service" "test" { + name = "acctestAS-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + app_service_plan_id = azurerm_app_service_plan.test.id + + source_control { + repo_url = "https://github.com/jackofallops/azure-app-service-static-site-tests.git" + branch = "%[5]s" + manual_integration = true + rollback_enabled = false + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, branch) +} + func testAccAzureRMAppService_ftpsState(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { @@ -4066,6 +4147,8 @@ resource "azurerm_app_service_plan" "test" { name = "acctestASP-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name + kind = "Linux" + reserved = true sku { tier = "Standard" @@ -4081,7 +4164,7 @@ resource "azurerm_app_service" "test" { site_config { always_on = true - linux_fx_version = "DOCKER|(golang:latest)" + linux_fx_version = "DOCKER|golang:latest" } app_settings = { diff --git a/azurerm/internal/services/web/tests/resource_arm_function_app_slot_test.go b/azurerm/internal/services/web/tests/resource_arm_function_app_slot_test.go index 671322ec6c53..a5044073f6c8 100644 --- a/azurerm/internal/services/web/tests/resource_arm_function_app_slot_test.go +++ b/azurerm/internal/services/web/tests/resource_arm_function_app_slot_test.go @@ -461,12 +461,47 @@ func TestAccAzureRMFunctionAppSlot_manyIpRestrictions(t *testing.T) { Config: testAccAzureRMFunctionAppSlot_manyIpRestrictions(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMFunctionAppSlotExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.0.ip_address", "10.10.10.10/32"), - resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.1.ip_address", "20.20.20.0/24"), - resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.2.ip_address", "30.30.0.0/16"), - resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.3.ip_address", "192.168.1.2/24"), ), }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_scmUseMainIPRestriction(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_function_app_slot", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMFunctionAppSlotDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMFunctionAppSlot_scmUseMainIPRestriction(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_scmIPRestrictionComplete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_function_app_slot", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMFunctionAppSlotDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMFunctionAppSlot_scmIPRestrictionComplete(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + ), + }, + data.ImportStep(), }, }) } @@ -642,6 +677,26 @@ func TestAccAzureRMFunctionAppSlot_minTls(t *testing.T) { }) } +func TestAccAzureRMFunctionAppSlot_preWarmedInstanceCount(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_function_app_slot", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMFunctionAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMFunctionAppSlot_preWarmedInstanceCount(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.pre_warmed_instance_count", "1"), + ), + }, + data.ImportStep(), + }, + }) +} + func testCheckAzureRMFunctionAppSlotDestroy(s *terraform.State) error { client := acceptance.AzureProvider.Meta().(*clients.Client).Web.AppServicesClient ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext @@ -700,26 +755,6 @@ func testCheckAzureRMFunctionAppSlotExists(slot string) resource.TestCheckFunc { } } -func TestAccAzureRMFunctionAppSlot_preWarmedInstanceCount(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_function_app_slot", "test") - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acceptance.PreCheck(t) }, - Providers: acceptance.SupportedProviders, - CheckDestroy: testCheckAzureRMFunctionAppDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAzureRMFunctionAppSlot_preWarmedInstanceCount(data), - Check: resource.ComposeTestCheckFunc( - testCheckAzureRMFunctionAppSlotExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.pre_warmed_instance_count", "1"), - ), - }, - data.ImportStep(), - }, - }) -} - func testAccAzureRMFunctionAppSlot_basic(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { @@ -1605,7 +1640,7 @@ resource "azurerm_function_app_slot" "test" { site_config { ip_restriction { - subnet_id = azurerm_subnet.test.id + virtual_network_subnet_id = azurerm_subnet.test.id } } } @@ -1736,6 +1771,125 @@ resource "azurerm_function_app_slot" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) } +func testAccAzureRMFunctionAppSlot_scmUseMainIPRestriction(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_function_app" "test" { + name = "acctestFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + app_service_plan_id = azurerm_app_service_plan.test.id + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key +} + +resource "azurerm_function_app_slot" "test" { + name = "acctestFASlot-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + app_service_plan_id = azurerm_app_service_plan.test.id + function_app_name = azurerm_function_app.test.name + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + site_config { + ip_restriction { + ip_address = "10.10.10.10/32" + } + + scm_use_main_ip_restriction = true + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func testAccAzureRMFunctionAppSlot_scmIPRestrictionComplete(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_function_app" "test" { + name = "acctestFA-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + app_service_plan_id = azurerm_app_service_plan.test.id + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key +} + +resource "azurerm_function_app_slot" "test" { + name = "acctestFASlot-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + app_service_plan_id = azurerm_app_service_plan.test.id + function_app_name = azurerm_function_app.test.name + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + site_config { + scm_ip_restriction { + ip_address = "10.10.10.10/32" + name = "test-restriction" + priority = 123 + action = "Allow" + } + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) +} + func testAccAzureRMFunctionAppSlot_tags(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/azurerm/internal/services/web/tests/resource_arm_function_app_test.go b/azurerm/internal/services/web/tests/resource_arm_function_app_test.go index 4b1c29d1c973..22d4a853e641 100644 --- a/azurerm/internal/services/web/tests/resource_arm_function_app_test.go +++ b/azurerm/internal/services/web/tests/resource_arm_function_app_test.go @@ -501,7 +501,7 @@ func TestAccAzureRMFunctionApp_updateIdentity(t *testing.T) { { Config: testAccAzureRMFunctionApp_basic(data), Check: resource.ComposeTestCheckFunc( - testCheckAzureRMAppServiceExists(data.ResourceName), + testCheckAzureRMFunctionAppExists(data.ResourceName), resource.TestCheckResourceAttr(data.ResourceName, "identity.#", "0"), ), }, @@ -585,7 +585,7 @@ func TestAccAzureRMFunctionApp_updateLogging(t *testing.T) { { Config: testAccAzureRMFunctionApp_basic(data), Check: resource.ComposeTestCheckFunc( - testCheckAzureRMAppServiceExists(data.ResourceName), + testCheckAzureRMFunctionAppExists(data.ResourceName), resource.TestCheckResourceAttr(data.ResourceName, "enable_builtin_logging", "true"), ), }, @@ -599,7 +599,7 @@ func TestAccAzureRMFunctionApp_updateLogging(t *testing.T) { { Config: testAccAzureRMFunctionApp_basic(data), Check: resource.ComposeTestCheckFunc( - testCheckAzureRMAppServiceExists(data.ResourceName), + testCheckAzureRMFunctionAppExists(data.ResourceName), resource.TestCheckResourceAttr(data.ResourceName, "enable_builtin_logging", "true"), ), }, @@ -620,7 +620,7 @@ func TestAccAzureRMFunctionApp_authSettings(t *testing.T) { { Config: testAccAzureRMFunctionApp_authSettings(data, tenantID), Check: resource.ComposeTestCheckFunc( - testCheckAzureRMAppServiceExists(data.ResourceName), + testCheckAzureRMFunctionAppExists(data.ResourceName), resource.TestCheckResourceAttr(data.ResourceName, "auth_settings.0.enabled", "true"), resource.TestCheckResourceAttr(data.ResourceName, "auth_settings.0.issuer", fmt.Sprintf("https://sts.windows.net/%s", tenantID)), resource.TestCheckResourceAttr(data.ResourceName, "auth_settings.0.runtime_version", "1.0"), @@ -829,10 +829,42 @@ func TestAccAzureRMFunctionApp_manyIpRestrictions(t *testing.T) { Config: testAccAzureRMFunctionApp_manyIpRestrictions(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMFunctionAppExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.0.ip_address", "10.10.10.10/32"), - resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.1.ip_address", "20.20.20.0/24"), - resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.2.ip_address", "30.30.0.0/16"), - resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.3.ip_address", "192.168.1.2/24"), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMFunctionApp_scmUseMainIPRestriction(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_function_app", "test") + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMFunctionAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMFunctionApp_scmUseMainIPRestriction(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMFunctionApp_scmOneIpRestriction(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_function_app", "test") + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMFunctionAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMFunctionApp_scmOneIpRestriction(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppExists(data.ResourceName), ), }, data.ImportStep(), @@ -873,6 +905,51 @@ func TestAccAzureRMFunctionApp_updateStorageAccountKey(t *testing.T) { }) } +func TestAccAzureRMFunctionApp_withSourceControl(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_function_app", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMFunctionAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMFunctionApp_withSourceControl(data, "main"), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMFunctionApp_sourceControlUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_function_app", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMFunctionAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMFunctionApp_withSourceControl(data, "main"), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMFunctionApp_withSourceControl(data, "development"), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + func testCheckAzureRMFunctionAppDestroy(s *terraform.State) error { client := acceptance.AzureProvider.Meta().(*clients.Client).Web.AppServicesClient ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext @@ -1034,6 +1111,54 @@ resource "azurerm_function_app" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomString) } +func testAccAzureRMFunctionApp_withSourceControl(data acceptance.TestData, branch string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_function_app" "test" { + name = "acctest-%[1]d-func" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + app_service_plan_id = azurerm_app_service_plan.test.id + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + source_control { + repo_url = "https://github.com/jackofallops/azure-app-service-static-site-tests.git" + branch = "%[4]s" + manual_integration = true + rollback_enabled = false + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, branch) +} + func testAccAzureRMFunctionApp_requiresImport(data acceptance.TestData) string { template := testAccAzureRMFunctionApp_basic(data) return fmt.Sprintf(` @@ -2393,7 +2518,7 @@ resource "azurerm_function_app" "test" { site_config { ip_restriction { - subnet_id = azurerm_subnet.test.id + virtual_network_subnet_id = azurerm_subnet.test.id } } } @@ -2504,6 +2629,102 @@ resource "azurerm_function_app" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) } +func testAccAzureRMFunctionApp_scmUseMainIPRestriction(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_function_app" "test" { + name = "acctest-%d-func" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + app_service_plan_id = azurerm_app_service_plan.test.id + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + site_config { + ip_restriction { + ip_address = "10.10.10.10/32" + } + scm_use_main_ip_restriction = true + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func testAccAzureRMFunctionApp_scmOneIpRestriction(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_function_app" "test" { + name = "acctest-%d-func" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + app_service_plan_id = azurerm_app_service_plan.test.id + storage_account_name = azurerm_storage_account.test.name + storage_account_access_key = azurerm_storage_account.test.primary_access_key + + site_config { + scm_ip_restriction { + ip_address = "10.10.10.10/32" + action = "Allow" + } + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) +} + func testAccAzureRMFunctionApp_deprecatedConnectionString(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/azurerm/internal/services/web/validate/app_service.go b/azurerm/internal/services/web/validate/app_service.go index 71b389e716ae..5d5b1ee55134 100644 --- a/azurerm/internal/services/web/validate/app_service.go +++ b/azurerm/internal/services/web/validate/app_service.go @@ -3,6 +3,8 @@ package validate import ( "fmt" "regexp" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/web/parse" ) func AppServiceName(v interface{}, k string) (warnings []string, errors []error) { @@ -14,3 +16,19 @@ func AppServiceName(v interface{}, k string) (warnings []string, errors []error) return warnings, errors } + +// AppServiceID validates that the specified ID is a valid App Service ID +func AppServiceID(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 + } + + if _, err := parse.AppServiceID(v); err != nil { + errors = append(errors, fmt.Errorf("Can not parse %q as a resource id: %v", k, err)) + return + } + + return warnings, errors +} diff --git a/azurerm/internal/services/web/validate/app_service_test.go b/azurerm/internal/services/web/validate/app_service_test.go new file mode 100644 index 000000000000..580be18c372f --- /dev/null +++ b/azurerm/internal/services/web/validate/app_service_test.go @@ -0,0 +1,51 @@ +package validate + +import ( + "testing" +) + +func TestValidateAppServiceID(t *testing.T) { + cases := []struct { + ID string + Valid bool + }{ + { + ID: "", + Valid: false, + }, + { + ID: "nonsense", + Valid: false, + }, + { + ID: "/subscriptions/00000000-0000-0000-0000-000000000000", + Valid: false, + }, + { + ID: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo", + Valid: false, + }, + { + ID: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/providers/Microsoft.Web", + Valid: false, + }, + { + ID: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/providers/Microsoft.Web/sites/duckduckgo", + Valid: true, + }, + { + ID: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/providers/Microsoft.Web/Sites/duckduckgo", + Valid: false, + }, + } + + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %q", tc.ID) + _, errors := AppServiceID(tc.ID, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/website/docs/d/app_service.html.markdown b/website/docs/d/app_service.html.markdown index 50a5f0b3dcd0..0e6b77da4b69 100644 --- a/website/docs/d/app_service.html.markdown +++ b/website/docs/d/app_service.html.markdown @@ -59,9 +59,11 @@ output "app_service_id" { * `possible_outbound_ip_addresses` - A comma separated list of outbound IP addresses - such as `52.23.25.3,52.143.43.12,52.143.43.17` - not all of which are necessarily in use. Superset of `outbound_ip_addresses`. +* `source_control` - A `source_control` block as defined below. + --- -`connection_string` supports the following: +A `connection_string` block exports the following: * `name` - The name of the Connection String. @@ -79,7 +81,7 @@ A `cors` block exports the following: --- -A `ip_restriction` block exports the following: +An `ip_restriction` block exports the following: * `ip_address` - The IP Address used for this IP Restriction. @@ -92,7 +94,7 @@ A `ip_restriction` block exports the following: * `action` - Does this restriction `Allow` or `Deny` access for this IP range? --- -A `scm_ip_restriction` block exports the following: +An `scm_ip_restriction` block exports the following: * `ip_address` - The IP Address used for this IP Restriction in CIDR notation. @@ -106,9 +108,9 @@ A `scm_ip_restriction` block exports the following: --- -`site_config` supports the following: +A `site_config` block exports the following: -* `always_on` - Is the app be loaded at all times? +* `always_on` - Is the app loaded at all times? * `app_command_line` - App command line to launch. @@ -160,6 +162,21 @@ A `scm_ip_restriction` block exports the following: * `websockets_enabled` - Are WebSockets enabled for this App Service? +--- + +A `source_control` block exports the following: + +* `repo_url` - The URL of the source code repository. + +* `branch` - The branch of the remote repository in use. + +* `manual_integration` - Limits to manual integration. + +* `rollback_enabled` - Is roll-back enabled for the repository. + +* `use_mercurial` - Uses Mercurial if `true`, otherwise uses Git. + + ## Timeouts The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: diff --git a/website/docs/d/function_app.html.markdown b/website/docs/d/function_app.html.markdown index 6a712ed2aa37..61750f89d998 100644 --- a/website/docs/d/function_app.html.markdown +++ b/website/docs/d/function_app.html.markdown @@ -48,12 +48,14 @@ The following attributes are exported: * `os_type` - A string indicating the Operating System type for this function app. -~> **NOTE:** This value will be `linux` for Linux Derivatives or an empty string for Windows. +~> **NOTE:** This value will be `linux` for Linux Derivatives, or an empty string for Windows. * `outbound_ip_addresses` - A comma separated list of outbound IP addresses. * `possible_outbound_ip_addresses` - A comma separated list of outbound IP addresses, not all of which are necessarily in use. Superset of `outbound_ip_addresses`. +* `source_control` - A `source_control` block as defined below. + --- The `connection_string` supports the following: @@ -69,6 +71,78 @@ The `site_credential` block exports the following: * `username` - The username which can be used to publish to this App Service * `password` - The password associated with the username, which can be used to publish to this App Service. +--- + +An `ip_restriction` block exports the following: + +* `ip_address` - The IP Address used for this IP Restriction. + +* `subnet_mask` - The Subnet mask used for this IP Restriction. + +* `name` - The name for this IP Restriction. + +* `priority` - The priority for this IP Restriction. + +* `action` - Does this restriction `Allow` or `Deny` access for this IP range? + +--- +An `scm_ip_restriction` block exports the following: + +* `ip_address` - The IP Address used for this IP Restriction in CIDR notation. + +* `virtual_network_subnet_id` - The Virtual Network Subnet ID used for this IP Restriction. + +* `name` - The name for this IP Restriction. + +* `priority` - The priority for this IP Restriction. + +* `action` - Allow or Deny access for this IP range. Defaults to Allow. + +--- + +A `site_config` block exports the following: + +* `always_on` - Is the app loaded at all times? + +* `cors` - A `cors` block as defined above. + +* `http2_enabled` - Is HTTP2 Enabled on this App Service? + +* `ftps_state` - State of FTP / FTPS service for this AppService. + +* `ip_restriction` - One or more `ip_restriction` blocks as defined above. + +* `pre_warmed_instance_count` - The number of pre-warmed instances for this function app. Only applicable to apps on the Premium plan. + +* `scm_use_main_ip_restriction` - IP security restrictions for scm to use main. + +* `scm_ip_restriction` - One or more `scm_ip_restriction` blocks as defined above. + +* `linux_fx_version` - Linux App Framework and version for the AppService. + +* `min_tls_version` - The minimum supported TLS version for this App Service. + +* `scm_type` - The type of Source Control enabled for this App Service. + +* `use_32_bit_worker_process` - Does the App Service run in 32 bit mode, rather than 64 bit mode? + +* `websockets_enabled` - Are WebSockets enabled for this App Service? + +--- + +A `source_control` block exports the following: + +* `repo_url` - The URL of the source code repository. + +* `branch` - The branch of the remote repository in use. + +* `manual_integration` - Limits to manual integration. + +* `rollback_enabled` - Is roll-back enabled for the repository. + +* `use_mercurial` - Uses Mercurial if `true`, otherwise uses Git. + + ## Timeouts The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: diff --git a/website/docs/r/app_service.html.markdown b/website/docs/r/app_service.html.markdown index f45e988eeddc..2c8e30553a0d 100644 --- a/website/docs/r/app_service.html.markdown +++ b/website/docs/r/app_service.html.markdown @@ -73,8 +73,6 @@ The following arguments are supported: * `auth_settings` - (Optional) A `auth_settings` block as defined below. -* `storage_account` - (Optional) One or more `storage_account` blocks as defined below. - * `backup` - (Optional) A `backup` block as defined below. * `connection_string` - (Optional) One or more `connection_string` blocks as defined below. @@ -85,15 +83,19 @@ The following arguments are supported: * `enabled` - (Optional) Is the App Service Enabled? +* `identity` - (Optional) A Managed Service Identity block as defined below. + * `https_only` - (Optional) Can the App Service only be accessed via HTTPS? Defaults to `false`. * `logs` - (Optional) A `logs` block as defined below. +* `storage_account` - (Optional) One or more `storage_account` blocks as defined below. + * `site_config` - (Optional) A `site_config` block as defined below. -* `tags` - (Optional) A mapping of tags to assign to the resource. +* `source_control` - (Optional) A Source Control block as defined below -* `identity` - (Optional) A Managed Service Identity block as defined below. +* `tags` - (Optional) A mapping of tags to assign to the resource. --- @@ -217,6 +219,8 @@ A `site_config` block supports the following: * `linux_fx_version` - (Optional) Linux App Framework and version for the App Service. Possible options are a Docker container (`DOCKER|`), a base-64 encoded Docker Compose file (`COMPOSE|${filebase64("compose.yml")}`) or a base-64 encoded Kubernetes Manifest (`KUBE|${filebase64("kubernetes.yml")}`). +~> **NOTE:** To set this property the App Service Plan to which the App belongs must be configured with `kind = "Linux"`, and `reserved = true` or the API will reject any value supplied. + * `windows_fx_version` - (Optional) The Windows Docker container image (`DOCKER|`) Additional examples of how to run Containers via the `azurerm_app_service` resource can be found in [the `./examples/app-service` directory within the Github Repository](https://github.com/terraform-providers/terraform-provider-azurerm/tree/master/examples/app-service). @@ -331,8 +335,6 @@ A `ip_restriction` block supports the following: --- ---- - A `scm_ip_restriction` block supports the following: * `ip_address` - (Optional) The IP Address used for this IP Restriction in CIDR notation. @@ -383,6 +385,20 @@ A `schedule` block supports the following: * `start_time` - (Optional) Sets when the schedule should start working. +--- + +A `source_control` block supports the following: + +* `repo_url` - (Required) The URL of the source code repository. + +* `branch` - (Optional) The branch of the remote repository to use. Defaults to 'master'. + +* `manual_integration` - (Optional) Limits to manual integration. Defaults to `false` if not specified. + +* `rollback_enabled` - (Optional) Enable roll-back for the repository. Defaults to `false` if not specified. + +* `use_mercurial` - (Optional) Use Mercurial if `true`, otherwise uses Git. + ## Attributes Reference The following attributes are exported: diff --git a/website/docs/r/app_service_slot.html.markdown b/website/docs/r/app_service_slot.html.markdown index cdb2daffb38c..2db5e76400ba 100644 --- a/website/docs/r/app_service_slot.html.markdown +++ b/website/docs/r/app_service_slot.html.markdown @@ -310,14 +310,17 @@ A `google` block supports the following: A `ip_restriction` block supports the following: -* `ip_address` - (Optional) The IP Address used for this IP Restriction. +* `ip_address` - (Optional) The IP Address used for this IP Restriction in CIDR notation. -* `subnet_mask` - (Optional) The Subnet mask used for this IP Restriction. Defaults to `255.255.255.255`. - -* `virtual_network_subnet_id` - (Optional.The Virtual Network Subnet ID used for this IP Restriction. +* `virtual_network_subnet_id` - (Optional) The Virtual Network Subnet ID used for this IP Restriction. -> **NOTE:** One of either `ip_address` or `virtual_network_subnet_id` must be specified +* `name` - (Optional) The name for this IP Restriction. + +* `priority` - (Optional) The priority for this IP Restriction. Restrictions are enforced in priority order. By default, priority is set to 65000 if not specified. + +* `action` - (Optional) Does this restriction `Allow` or `Deny` access for this IP range. Defaults to `Allow`. --- diff --git a/website/docs/r/function_app.html.markdown b/website/docs/r/function_app.html.markdown index e25de334087c..2fa82947283d 100644 --- a/website/docs/r/function_app.html.markdown +++ b/website/docs/r/function_app.html.markdown @@ -132,41 +132,41 @@ The following arguments are supported: * `app_service_plan_id` - (Required) The ID of the App Service Plan within which to create this Function App. -* `storage_connection_string`- (Required) The connection string to the backend storage account which will be used by this Function App (such as the dashboard, logs). Typically set to the `primary_connection_string` of a storage account resource. - -* `storage_account_name` - (Required) The backend storage account name which will be used by this Function App (such as the dashboard, logs). +* `app_settings` - (Optional) A map of key-value pairs for [App Settings](https://docs.microsoft.com/en-us/azure/azure-functions/functions-app-settings) and custom values. -* `storage_account_access_key` - (Required) The access key which will be used to access the backend storage account for the Function App. +* `auth_settings` - (Optional) A `auth_settings` block as defined below. -* `app_settings` - (Optional) A map of key-value pairs for [App Settings](https://docs.microsoft.com/en-us/azure/azure-functions/functions-app-settings) and custom values. +* `connection_string` - (Optional) An `connection_string` block as defined below. -~> **Note:** When integrating a `CI/CD pipeline` and expecting to run from a deployed package in `Azure` you must seed your `app settings` as part of terraform code for function app to be successfully deployed. `Important Default key pairs`: (`"WEBSITE_RUN_FROM_PACKAGE" = ""`, `"FUNCTIONS_WORKER_RUNTIME" = "node"` (or python, etc), `"WEBSITE_NODE_DEFAULT_VERSION" = "10.14.1"`, `"APPINSIGHTS_INSTRUMENTATIONKEY" = ""`). +* `client_affinity_enabled` - (Optional) Should the Function App send session affinity cookies, which route client requests in the same session to the same instance? -~> **Note:** When using an App Service Plan in the `Free` or `Shared` Tiers `use_32_bit_worker_process` must be set to `true`. +* `daily_memory_time_quota` - (Optional) The amount of memory in gigabyte-seconds that your application is allowed to consume per day. Setting this value only affects function apps under the consumption plan. Defaults to `0`. -* `auth_settings` - (Optional) A `auth_settings` block as defined below. +* `enabled` - (Optional) Is the Function App enabled? * `enable_builtin_logging` - (Optional) Should the built-in logging of this Function App be enabled? Defaults to `true`. -* `connection_string` - (Optional) An `connection_string` block as defined below. +* `https_only` - (Optional) Can the Function App only be accessed via HTTPS? Defaults to `false`. + +* `identity` - (Optional) An `identity` block as defined below. * `os_type` - (Optional) A string indicating the Operating System type for this function app. -~> **NOTE:** This value will be `linux` for Linux Derivatives or an empty string for Windows (default). When set to `linux` you must also set `azurerm_app_service_plan` arguments as `kind = "FunctionApp"` and `reserved = true` +~> **NOTE:** This value will be `linux` for Linux derivatives, or an empty string for Windows (default). When set to `linux` you must also set `azurerm_app_service_plan` arguments as `kind = "FunctionApp"` and `reserved = true` -* `client_affinity_enabled` - (Optional) Should the Function App send session affinity cookies, which route client requests in the same session to the same instance? +* `site_config` - (Optional) A `site_config` object as defined below. -* `enabled` - (Optional) Is the Function App enabled? +* `source_control` - (Optional) A `source_control` block, as defined below. -* `https_only` - (Optional) Can the Function App only be accessed via HTTPS? Defaults to `false`. +* `storage_account_name` - (Required) The backend storage account name which will be used by this Function App (such as the dashboard, logs). -* `version` - (Optional) The runtime version associated with the Function App. Defaults to `~1`. +* `storage_account_access_key` - (Required) The access key which will be used to access the backend storage account for the Function App. -* `daily_memory_time_quota` - (Optional) The amount of memory in gigabyte-seconds that your application is allowed to consume per day. Setting this value only affects function apps under the consumption plan. Defaults to `0`. +~> **Note:** When integrating a `CI/CD pipeline` and expecting to run from a deployed package in `Azure` you must seed your `app settings` as part of terraform code for function app to be successfully deployed. `Important Default key pairs`: (`"WEBSITE_RUN_FROM_PACKAGE" = ""`, `"FUNCTIONS_WORKER_RUNTIME" = "node"` (or python, etc), `"WEBSITE_NODE_DEFAULT_VERSION" = "10.14.1"`, `"APPINSIGHTS_INSTRUMENTATIONKEY" = ""`). -* `site_config` - (Optional) A `site_config` object as defined below. +~> **Note:** When using an App Service Plan in the `Free` or `Shared` Tiers `use_32_bit_worker_process` must be set to `true`. -* `identity` - (Optional) An `identity` block as defined below. +* `version` - (Optional) The runtime version associated with the Function App. Defaults to `~1`. * `tags` - (Optional) A mapping of tags to assign to the resource. @@ -175,7 +175,9 @@ The following arguments are supported: `connection_string` supports the following: * `name` - (Required) The name of the Connection String. + * `type` - (Required) The type of the Connection String. Possible values are `APIHub`, `Custom`, `DocDb`, `EventHub`, `MySQL`, `NotificationHub`, `PostgreSQL`, `RedisCache`, `ServiceBus`, `SQLAzure` and `SQLServer`. + * `value` - (Required) The value for the Connection String. --- @@ -184,27 +186,39 @@ The following arguments are supported: * `always_on` - (Optional) Should the Function App be loaded at all times? Defaults to `false`. -* `use_32_bit_worker_process` - (Optional) Should the Function App run in 32 bit mode, rather than 64 bit mode? Defaults to `true`. +* `cors` - (Optional) A `cors` block as defined below. -~> **Note:** when using an App Service Plan in the `Free` or `Shared` Tiers `use_32_bit_worker_process` must be set to `true`. +* `ftps_state` - (Optional) State of FTP / FTPS service for this function app. Possible values include: `AllAllowed`, `FtpsOnly` and `Disabled`. -* `websockets_enabled` - (Optional) Should WebSockets be enabled? +* `http2_enabled` - (Optional) Specifies whether or not the http2 protocol should be enabled. Defaults to `false`. -* `linux_fx_version` - (Optional) Linux App Framework and version for the AppService, e.g. `DOCKER|(golang:latest)`. +* `ip_restriction` - (Optional) A [List of objects](/docs/configuration/attr-as-blocks.html) representing ip restrictions as defined below. -* `http2_enabled` - (Optional) Specifies whether or not the http2 protocol should be enabled. Defaults to `false`. +-> **NOTE** User has to explicitly set `ip_restriction` to empty slice (`[]`) to remove it. -* `min_tls_version` - (Optional) The minimum supported TLS version for the function app. Possible values are `1.0`, `1.1`, and `1.2`. Defaults to `1.2` for new function apps. +* `linux_fx_version` - (Optional) Linux App Framework and version for the AppService, e.g. `DOCKER|(golang:latest)`. -* `ftps_state` - (Optional) State of FTP / FTPS service for this function app. Possible values include: `AllAllowed`, `FtpsOnly` and `Disabled`. +* `min_tls_version` - (Optional) The minimum supported TLS version for the function app. Possible values are `1.0`, `1.1`, and `1.2`. Defaults to `1.2` for new function apps. * `pre_warmed_instance_count` - (Optional) The number of pre-warmed instances for this function app. Only affects apps on the Premium plan. -* `cors` - (Optional) A `cors` block as defined below. +* `scm_ip_restriction` - (Optional) A [List of objects](/docs/configuration/attr-as-blocks.html) representing ip restrictions as defined below. -* `ip_restriction` - (Optional) A [List of objects](/docs/configuration/attr-as-blocks.html) representing ip restrictions as defined below. +-> **NOTE** User has to explicitly set `scm_ip_restriction` to empty slice (`[]`) to remove it. --> **NOTE** User has to explicitly set `ip_restriction` to empty slice (`[]`) to remove it. +* `scm_type` - (Optional) The type of Source Control used by the Function App. Valid values include: `BitBucketGit`, `BitBucketHg`, `CodePlexGit`, `CodePlexHg`, `Dropbox`, `ExternalGit`, `ExternalHg`, `GitHub`, `LocalGit`, `None` (dafault), `OneDrive`, `Tfs`, `VSO`, and `VSTSRM` + +~> **NOTE:** This setting is incompatible with the `source_control` block which updates this value based on the setting provided. + +* `scm_use_main_ip_restriction` - (Optional) IP security restrictions for scm to use main. Defaults to false. + +-> **NOTE** Any `scm_ip_restriction` blocks configured are ignored by the service when `scm_use_main_ip_restriction` is set to `true`. Any scm restrictions will become active if this is subsequently set to `false` or removed. + +* `use_32_bit_worker_process` - (Optional) Should the Function App run in 32 bit mode, rather than 64 bit mode? Defaults to `true`. + +~> **Note:** when using an App Service Plan in the `Free` or `Shared` Tiers `use_32_bit_worker_process` must be set to `true`. + +* `websockets_enabled` - (Optional) Should WebSockets be enabled? --- @@ -302,11 +316,47 @@ A `microsoft` block supports the following: A `ip_restriction` block supports the following: -* `ip_address` - (Optional) The IP Address CIDR notation used for this IP Restriction. +* `ip_address` - (Optional) The IP Address used for this IP Restriction in CIDR notation. + +* `virtual_network_subnet_id` - (Optional) The Virtual Network Subnet ID used for this IP Restriction. + +-> **NOTE:** One of either `ip_address` or `virtual_network_subnet_id` must be specified -* `subnet_id` - (Optional) The Subnet ID used for this IP Restriction. +* `name` - (Optional) The name for this IP Restriction. --> **NOTE:** One of either `ip_address` or `subnet_id` must be specified +* `priority` - (Optional) The priority for this IP Restriction. Restrictions are enforced in priority order. By default, the priority is set to 65000 if not specified. + +* `action` - (Optional) Does this restriction `Allow` or `Deny` access for this IP range. Defaults to `Allow`. + +--- + +A `scm_ip_restriction` block supports the following: + +* `ip_address` - (Optional) The IP Address used for this IP Restriction in CIDR notation. + +* `virtual_network_subnet_id` - (Optional) The Virtual Network Subnet ID used for this IP Restriction. + +-> **NOTE:** One of either `ip_address` or `virtual_network_subnet_id` must be specified + +* `name` - (Optional) The name for this IP Restriction. + +* `priority` - (Optional) The priority for this IP Restriction. Restrictions are enforced in priority order. By default, priority is set to 65000 if not specified. + +* `action` - (Optional) Allow or Deny access for this IP range. Defaults to Allow. + +--- + +A `source_control` block supports the following: + +* `repo_url` - (Required) The URL of the source code repository. + +* `branch` - (Optional) The branch of the remote repository to use. Defaults to 'master'. + +* `manual_integration` - (Optional) Limits to manual integration. Defaults to `false` if not specified. + +* `rollback_enabled` - (Optional) Enable roll-back for the repository. Defaults to `false` if not specified. + +* `use_mercurial` - (Optional) Use Mercurial if `true`, otherwise uses Git. ## Attributes Reference @@ -334,10 +384,12 @@ The `identity` block exports the following: * `tenant_id` - The Tenant ID for the Service Principal associated with the Managed Service Identity of this App Service. +--- The `site_credential` block exports the following: * `username` - The username which can be used to publish to this App Service + * `password` - The password associated with the username, which can be used to publish to this App Service. ## Timeouts diff --git a/website/docs/r/function_app_slot.html.markdown b/website/docs/r/function_app_slot.html.markdown index 4372dd92af1d..be3f25e4f91b 100644 --- a/website/docs/r/function_app_slot.html.markdown +++ b/website/docs/r/function_app_slot.html.markdown @@ -240,11 +240,17 @@ A `microsoft` block supports the following: A `ip_restriction` block supports the following: -* `ip_address` - (Optional) The IP Address CIDR notation used for this IP Restriction. +* `ip_address` - (Optional) The IP Address used for this IP Restriction in CIDR notation. -* `subnet_id` - (Optional) The Subnet ID used for this IP Restriction. +* `virtual_network_subnet_id` - (Optional) The Virtual Network Subnet ID used for this IP Restriction. --> **NOTE:** One of either `ip_address` or `subnet_id` must be specified +-> **NOTE:** One of either `ip_address` or `virtual_network_subnet_id` must be specified + +* `name` - (Optional) The name for this IP Restriction. + +* `priority` - (Optional) The priority for this IP Restriction. Restrictions are enforced in priority order. By default, priority is set to 65000 if not specified. + +* `action` - (Optional) Does this restriction `Allow` or `Deny` access for this IP range. Defaults to `Allow`. ## Attributes Reference