diff --git a/azurerm/internal/services/web/data_source_function_app.go b/azurerm/internal/services/web/data_source_function_app.go index bf4eb6e7a738..1c75bdd9dc6d 100644 --- a/azurerm/internal/services/web/data_source_function_app.go +++ b/azurerm/internal/services/web/data_source_function_app.go @@ -8,6 +8,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" + webValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/web/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" @@ -25,7 +26,7 @@ func dataSourceArmFunctionApp() *schema.Resource { "name": { Type: schema.TypeString, Required: true, - ValidateFunc: validateAppServiceName, + ValidateFunc: webValidate.AppServiceName, }, "resource_group_name": azure.SchemaResourceGroupNameForDataSource(), diff --git a/azurerm/internal/services/web/parse/function_app_slot.go b/azurerm/internal/services/web/parse/function_app_slot.go new file mode 100644 index 000000000000..5672365f70a8 --- /dev/null +++ b/azurerm/internal/services/web/parse/function_app_slot.go @@ -0,0 +1,40 @@ +package parse + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type FunctionAppSlotResourceID struct { + ResourceGroup string + FunctionAppName string + Name string +} + +func FunctionAppSlotID(input string) (*FunctionAppSlotResourceID, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, fmt.Errorf("[ERROR] Unable to parse App Service Slot ID %q: %+v", input, err) + } + + slot := FunctionAppSlotResourceID{ + ResourceGroup: id.ResourceGroup, + FunctionAppName: id.Path["sites"], + Name: id.Path["slots"], + } + + if slot.FunctionAppName, err = id.PopSegment("sites"); err != nil { + return nil, err + } + + if slot.Name, err = id.PopSegment("slots"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &slot, nil +} diff --git a/azurerm/internal/services/web/registration.go b/azurerm/internal/services/web/registration.go index 282159c5e36e..60ae99de4652 100644 --- a/azurerm/internal/services/web/registration.go +++ b/azurerm/internal/services/web/registration.go @@ -44,5 +44,6 @@ func (r Registration) SupportedResources() map[string]*schema.Resource { "azurerm_app_service_virtual_network_swift_connection": resourceArmAppServiceVirtualNetworkSwiftConnection(), "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 ba625f76bc47..6cc4c00455c3 100644 --- a/azurerm/internal/services/web/resource_arm_app_service.go +++ b/azurerm/internal/services/web/resource_arm_app_service.go @@ -3,7 +3,6 @@ package web import ( "fmt" "log" - "regexp" "time" "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2019-08-01/web" @@ -14,6 +13,7 @@ import ( "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/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" @@ -44,7 +44,7 @@ func resourceArmAppService() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateFunc: validateAppServiceName, + ValidateFunc: validate.AppServiceName, }, "identity": azure.SchemaAppServiceIdentity(), @@ -760,16 +760,6 @@ func flattenAppServiceAppSettings(input map[string]*string) map[string]string { return output } -func validateAppServiceName(v interface{}, k string) (warnings []string, errors []error) { - value := v.(string) - - if matched := regexp.MustCompile(`^[0-9a-zA-Z-]{1,60}$`).Match([]byte(value)); !matched { - errors = append(errors, fmt.Errorf("%q may only contain alphanumeric characters and dashes and up to 60 characters in length", k)) - } - - return warnings, errors -} - func flattenAppServiceSiteCredential(input *web.UserProperties) []interface{} { results := make([]interface{}, 0) result := make(map[string]interface{}) 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 e134aa8b1ab1..abf2a96daf91 100644 --- a/azurerm/internal/services/web/resource_arm_app_service_slot.go +++ b/azurerm/internal/services/web/resource_arm_app_service_slot.go @@ -13,6 +13,7 @@ import ( "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" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" @@ -42,7 +43,7 @@ func resourceArmAppServiceSlot() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateFunc: validateAppServiceName, + ValidateFunc: webValidate.AppServiceName, }, "resource_group_name": azure.SchemaResourceGroupName(), diff --git a/azurerm/internal/services/web/resource_arm_function_app.go b/azurerm/internal/services/web/resource_arm_function_app.go index fccdac4a91ba..47870acde88c 100644 --- a/azurerm/internal/services/web/resource_arm_function_app.go +++ b/azurerm/internal/services/web/resource_arm_function_app.go @@ -17,6 +17,7 @@ import ( "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" + 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" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" @@ -48,7 +49,7 @@ func resourceArmFunctionApp() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateFunc: validateAppServiceName, + ValidateFunc: webValidate.AppServiceName, }, "resource_group_name": azure.SchemaResourceGroupName(), diff --git a/azurerm/internal/services/web/resource_arm_function_app_slot.go b/azurerm/internal/services/web/resource_arm_function_app_slot.go new file mode 100644 index 000000000000..8f4a2c2df6ea --- /dev/null +++ b/azurerm/internal/services/web/resource_arm_function_app_slot.go @@ -0,0 +1,1004 @@ +package web + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "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/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" + 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" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmFunctionAppSlot() *schema.Resource { + return &schema.Resource{ + Create: resourceArmFunctionAppSlotCreate, + Read: resourceArmFunctionAppSlotRead, + Update: resourceArmFunctionAppSlotUpdate, + Delete: resourceArmFunctionAppSlotDelete, + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.FunctionAppSlotID(id) + return err + }), + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "location": azure.SchemaLocation(), + + "identity": azure.SchemaAppServiceIdentity(), + + "function_app_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: webValidate.AppServiceName, + }, + + "app_service_plan_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: ValidateAppServicePlanID, + }, + + "version": { + Type: schema.TypeString, + Optional: true, + Default: "~1", + }, + + "storage_account_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: storage.ValidateArmStorageAccountName, + }, + + "storage_account_access_key": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + ValidateFunc: validation.NoZeroValues, + }, + + "app_settings": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + + "daily_memory_time_quota": { + Type: schema.TypeInt, + Optional: true, + }, + + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "enable_builtin_logging": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "https_only": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "os_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "linux", + }, false), + }, + + "client_affinity_enabled": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + + "connection_string": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + }, + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.APIHub), + string(web.Custom), + string(web.DocDb), + string(web.EventHub), + string(web.MySQL), + string(web.NotificationHub), + string(web.PostgreSQL), + string(web.RedisCache), + string(web.ServiceBus), + string(web.SQLAzure), + string(web.SQLServer), + }, true), + DiffSuppressFunc: suppress.CaseDifference, + }, + }, + }, + }, + + "default_hostname": { + Type: schema.TypeString, + Computed: true, + }, + + "kind": { + Type: schema.TypeString, + Computed: true, + }, + + "outbound_ip_addresses": { + Type: schema.TypeString, + Computed: true, + }, + + "possible_outbound_ip_addresses": { + Type: schema.TypeString, + 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(), + }, + }, + }, + + "auth_settings": azure.SchemaAppServiceAuthSettings(), + + "site_credential": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "username": { + Type: schema.TypeString, + Computed: true, + }, + "password": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + }, + }, + }, + }, + + "tags": tags.Schema(), + }, + } +} + +func resourceArmFunctionAppSlotCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Web.AppServicesClient + endpointSuffix := meta.(*clients.Client).Account.Environment.StorageEndpointSuffix + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + log.Printf("[INFO] preparing arguments for AzureRM Function App Slot creation.") + + slot := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + functionAppName := d.Get("function_app_name").(string) + + if d.IsNewResource() { + existing, err := client.GetSlot(ctx, resourceGroup, functionAppName, slot) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of existing Slot %q (Function App %q / Resource Group %q): %s", slot, functionAppName, resourceGroup, err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_function_app_slot", *existing.ID) + } + } + + location := azure.NormalizeLocation(d.Get("location").(string)) + kind := "functionapp" + if osTypeRaw, ok := d.GetOk("os_type"); ok { + osType := osTypeRaw.(string) + if osType == "linux" { + kind = "functionapp,linux" + } + } + + appServicePlanID := d.Get("app_service_plan_id").(string) + enabled := d.Get("enabled").(bool) + clientAffinityEnabled := d.Get("client_affinity_enabled").(bool) + httpsOnly := d.Get("https_only").(bool) + dailyMemoryTimeQuota := d.Get("daily_memory_time_quota").(int) + t := d.Get("tags").(map[string]interface{}) + appServiceTier, err := getFunctionAppSlotServiceTier(ctx, appServicePlanID, meta) + if err != nil { + return err + } + + basicAppSettings := getBasicFunctionAppSlotAppSettings(d, appServiceTier, endpointSuffix) + + siteConfig, err := expandFunctionAppSlotSiteConfig(d) + if err != nil { + return fmt.Errorf("Error expanding `site_config` for Function App Slot %q (Resource Group %q): %s", slot, resourceGroup, err) + } + + siteConfig.AppSettings = &basicAppSettings + + siteEnvelope := web.Site{ + Kind: &kind, + Location: &location, + Tags: tags.Expand(t), + SiteProperties: &web.SiteProperties{ + ServerFarmID: utils.String(appServicePlanID), + Enabled: utils.Bool(enabled), + ClientAffinityEnabled: utils.Bool(clientAffinityEnabled), + HTTPSOnly: utils.Bool(httpsOnly), + DailyMemoryTimeQuota: utils.Int32(int32(dailyMemoryTimeQuota)), + SiteConfig: &siteConfig, + }, + } + + if _, ok := d.GetOk("identity"); ok { + appServiceIdentityRaw := d.Get("identity").([]interface{}) + appServiceIdentity := azure.ExpandAppServiceIdentity(appServiceIdentityRaw) + siteEnvelope.Identity = appServiceIdentity + } + + createFuture, err := client.CreateOrUpdateSlot(ctx, resourceGroup, functionAppName, siteEnvelope, slot) + if err != nil { + return err + } + + err = createFuture.WaitForCompletionRef(ctx, client.Client) + if err != nil { + return err + } + + read, err := client.GetSlot(ctx, resourceGroup, functionAppName, slot) + if err != nil { + return err + } + if read.ID == nil { + return fmt.Errorf("Cannot read ID for Slot %q (Function App %q / Resource Group %q) ID", slot, functionAppName, resourceGroup) + } + + d.SetId(*read.ID) + + authSettingsRaw := d.Get("auth_settings").([]interface{}) + authSettings := azure.ExpandAppServiceAuthSettings(authSettingsRaw) + + auth := web.SiteAuthSettings{ + ID: read.ID, + SiteAuthSettingsProperties: &authSettings} + + if _, err := client.UpdateAuthSettingsSlot(ctx, resourceGroup, functionAppName, auth, slot); err != nil { + return fmt.Errorf("Error updating auth settings for Slot %q (Function App Slot %q / Resource Group %q): %+s", slot, functionAppName, resourceGroup, err) + } + + return resourceArmFunctionAppSlotUpdate(d, meta) +} + +func resourceArmFunctionAppSlotUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Web.AppServicesClient + endpointSuffix := meta.(*clients.Client).Account.Environment.StorageEndpointSuffix + ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.FunctionAppSlotID(d.Id()) + if err != nil { + return err + } + + location := azure.NormalizeLocation(d.Get("location").(string)) + kind := "functionapp" + if osTypeRaw, ok := d.GetOk("os_type"); ok { + osType := osTypeRaw.(string) + if osType == "Linux" { + kind = "functionapp,linux" + } + } + appServicePlanID := d.Get("app_service_plan_id").(string) + enabled := d.Get("enabled").(bool) + clientAffinityEnabled := d.Get("client_affinity_enabled").(bool) + httpsOnly := d.Get("https_only").(bool) + dailyMemoryTimeQuota := d.Get("daily_memory_time_quota").(int) + t := d.Get("tags").(map[string]interface{}) + + appServiceTier, err := getFunctionAppSlotServiceTier(ctx, appServicePlanID, meta) + if err != nil { + return err + } + + basicAppSettings := getBasicFunctionAppSlotAppSettings(d, appServiceTier, endpointSuffix) + + siteConfig, err := expandFunctionAppSlotSiteConfig(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) + } + + siteConfig.AppSettings = &basicAppSettings + + siteEnvelope := web.Site{ + Kind: &kind, + Location: &location, + Tags: tags.Expand(t), + SiteProperties: &web.SiteProperties{ + ServerFarmID: utils.String(appServicePlanID), + Enabled: utils.Bool(enabled), + ClientAffinityEnabled: utils.Bool(clientAffinityEnabled), + HTTPSOnly: utils.Bool(httpsOnly), + DailyMemoryTimeQuota: utils.Int32(int32(dailyMemoryTimeQuota)), + SiteConfig: &siteConfig, + }, + } + + if _, ok := d.GetOk("identity"); ok { + appServiceIdentityRaw := d.Get("identity").([]interface{}) + appServiceIdentity := azure.ExpandAppServiceIdentity(appServiceIdentityRaw) + siteEnvelope.Identity = appServiceIdentity + } + + future, err := client.CreateOrUpdateSlot(ctx, id.ResourceGroup, id.FunctionAppName, siteEnvelope, id.Name) + if err != nil { + return fmt.Errorf("Error updating Slot %q (Function App %q / Resource Group %q): %s", id.Name, id.FunctionAppName, id.ResourceGroup, err) + } + + err = future.WaitForCompletionRef(ctx, client.Client) + if err != nil { + return fmt.Errorf("Error waiting for update of Slot %q (Function App %q / Resource Group %q): %s", id.Name, id.FunctionAppName, id.ResourceGroup, err) + } + + appSettings, err := expandFunctionAppSlotAppSettings(d, appServiceTier, endpointSuffix) + if err != nil { + return err + } + settings := web.StringDictionary{ + Properties: appSettings, + } + + if _, err = client.UpdateApplicationSettingsSlot(ctx, id.ResourceGroup, id.FunctionAppName, settings, id.Name); err != nil { + return fmt.Errorf("Error updating Application Settings for Function App Slot %q (Function App %q / Resource Group %q): %+v", id.Name, id.FunctionAppName, id.ResourceGroup, err) + } + + if d.HasChange("site_config") { + siteConfig, err := expandFunctionAppSlotSiteConfig(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) + } + siteConfigResource := web.SiteConfigResource{ + SiteConfig: &siteConfig, + } + if _, err := client.CreateOrUpdateConfigurationSlot(ctx, id.ResourceGroup, id.FunctionAppName, siteConfigResource, id.Name); err != nil { + return fmt.Errorf("Error updating Configuration for Slot %q (Function App %q / Resource Group %q): %+v", id.Name, id.FunctionAppName, id.ResourceGroup, err) + } + } + + if d.HasChange("auth_settings") { + authSettingsRaw := d.Get("auth_settings").([]interface{}) + authSettingsProperties := azure.ExpandAppServiceAuthSettings(authSettingsRaw) + authSettings := web.SiteAuthSettings{ + ID: utils.String(d.Id()), + SiteAuthSettingsProperties: &authSettingsProperties, + } + + if _, err := client.UpdateAuthSettingsSlot(ctx, id.ResourceGroup, id.FunctionAppName, authSettings, id.Name); err != nil { + return fmt.Errorf("Error updating Authentication Settings for Slot %q (Function App %q / Resource Group %q): %+v", id.Name, id.FunctionAppName, id.ResourceGroup, err) + } + } + + if d.HasChange("connection_string") { + // update the ConnectionStrings + connectionStrings := expandFunctionAppSlotConnectionStrings(d) + properties := web.ConnectionStringDictionary{ + Properties: connectionStrings, + } + + if _, err := client.UpdateConnectionStringsSlot(ctx, id.ResourceGroup, id.FunctionAppName, properties, id.Name); err != nil { + return fmt.Errorf("Error updating Connection Strings for Slot %q (Function App %q / Resource Group %q): %+v", id.Name, id.FunctionAppName, id.ResourceGroup, err) + } + } + + return resourceArmFunctionAppSlotRead(d, meta) +} + +func resourceArmFunctionAppSlotRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Web.AppServicesClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.FunctionAppSlotID(d.Id()) + if err != nil { + return err + } + + resp, err := client.GetSlot(ctx, id.ResourceGroup, id.FunctionAppName, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[DEBUG] Function App Slot %q (Function App %q / Resource Group %q) was not found - removing from state", id.Name, id.FunctionAppName, id.ResourceGroup) + d.SetId("") + return nil + } + return fmt.Errorf("Error makeing read request on AzureRM Function App Slot %q (Function App %q / Resource Group %q): %s", id.Name, id.FunctionAppName, id.ResourceGroup, err) + } + + appSettingsResp, err := client.ListApplicationSettingsSlot(ctx, id.ResourceGroup, id.FunctionAppName, id.Name) + if err != nil { + if utils.ResponseWasNotFound(appSettingsResp.Response) { + log.Printf("[DEBUG] Application Settings of AzureRM Function App Slot %q (Function App %q / Resource Group %q) were not found", id.Name, id.FunctionAppName, id.ResourceGroup) + d.SetId("") + return nil + } + return fmt.Errorf("Error making Read request on AzureRM Function App Slot %q (Function App %q / Resource Group %q) AppSettings: %+v", id.Name, id.FunctionAppName, id.ResourceGroup, err) + } + + connectionStringsResp, err := client.ListConnectionStringsSlot(ctx, id.ResourceGroup, id.FunctionAppName, id.Name) + if err != nil { + return fmt.Errorf("Error making Read request on AzureRM Function App Slot %q (Function App %q / Resource Group %q) ConnectionStrings: %+v", id.Name, id.FunctionAppName, id.ResourceGroup, err) + } + + siteCredFuture, err := client.ListPublishingCredentialsSlot(ctx, id.ResourceGroup, id.FunctionAppName, id.Name) + if err != nil { + return err + } + err = siteCredFuture.WaitForCompletionRef(ctx, client.Client) + if err != nil { + return err + } + siteCredResp, err := siteCredFuture.Result(*client) + if err != nil { + return fmt.Errorf("Error making Read request on AzureRM Function App Slot %q (Function App %q / Resource Group %q) Site Credentials: %+v", id.Name, id.FunctionAppName, id.ResourceGroup, err) + } + authResp, err := client.GetAuthSettingsSlot(ctx, id.ResourceGroup, id.FunctionAppName, id.Name) + if err != nil { + return fmt.Errorf("Error retrieving the AuthSettings for AzureRM Function App Slot %q (Function App %q / Resource Group %q): %+v", id.Name, id.FunctionAppName, id.ResourceGroup, err) + } + + d.Set("name", id.Name) + d.Set("resource_group_name", id.ResourceGroup) + d.Set("function_app_name", id.FunctionAppName) + d.Set("kind", resp.Kind) + osType := "" + if v := resp.Kind; v != nil && strings.Contains(*v, "linux") { + osType = "linux" + } + d.Set("os_type", osType) + + if location := resp.Location; location != nil { + d.Set("location", azure.NormalizeLocation(*location)) + } + + if props := resp.SiteProperties; props != nil { + d.Set("app_service_plan_id", props.ServerFarmID) + d.Set("enabled", props.Enabled) + d.Set("default_hostname", props.DefaultHostName) + d.Set("https_only", props.HTTPSOnly) + d.Set("daily_memory_time_quota", props.DailyMemoryTimeQuota) + d.Set("outbound_ip_addresses", props.OutboundIPAddresses) + d.Set("possible_outbound_ip_addresses", props.PossibleOutboundIPAddresses) + d.Set("client_affinity_enabled", props.ClientAffinityEnabled) + } + + appSettings := flattenAppServiceAppSettings(appSettingsResp.Properties) + + connectionString := appSettings["AzureWebJobsStorage"] + + // This teases out the necessary attributes from the storage connection string + connectionStringParts := strings.Split(connectionString, ";") + for _, part := range connectionStringParts { + if strings.HasPrefix(part, "AccountName") { + accountNameParts := strings.Split(part, "AccountName=") + if len(accountNameParts) > 1 { + d.Set("storage_account_name", accountNameParts[1]) + } + } + if strings.HasPrefix(part, "AccountKey") { + accountKeyParts := strings.Split(part, "AccountKey=") + if len(accountKeyParts) > 1 { + d.Set("storage_account_access_key", accountKeyParts[1]) + } + } + } + + d.Set("version", appSettings["FUNCTIONS_EXTENSION_VERSION"]) + + dashboard, ok := appSettings["AzureWebJobsDashboard"] + d.Set("enable_builtin_logging", ok && dashboard != "") + + delete(appSettings, "AzureWebJobsDashboard") + delete(appSettings, "AzureWebJobsStorage") + delete(appSettings, "FUNCTIONS_EXTENSION_VERSION") + delete(appSettings, "WEBSITE_CONTENTSHARE") + delete(appSettings, "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING") + + if err = d.Set("app_settings", appSettings); err != nil { + return err + } + if err = d.Set("connection_string", flattenFunctionAppSlotConnectionStrings(connectionStringsResp.Properties)); err != nil { + return err + } + + identity := azure.FlattenAppServiceIdentity(resp.Identity) + if err := d.Set("identity", identity); err != nil { + return fmt.Errorf("Error setting `identity`: %s", err) + } + + configResp, err := client.GetConfigurationSlot(ctx, id.ResourceGroup, id.FunctionAppName, id.Name) + if err != nil { + return fmt.Errorf("Error making Read request on AzureRM Function App Configuration %q: %+v", id.Name, err) + } + + siteConfig := flattenFunctionAppSlotSiteConfig(configResp.SiteConfig) + if err = d.Set("site_config", siteConfig); err != nil { + return err + } + + authSettings := azure.FlattenAppServiceAuthSettings(authResp.SiteAuthSettingsProperties) + if err := d.Set("auth_settings", authSettings); err != nil { + return fmt.Errorf("Error setting `auth_settings`: %s", err) + } + + siteCred := flattenFunctionAppSlotSiteCredential(siteCredResp.UserProperties) + if err = d.Set("site_credential", siteCred); err != nil { + return err + } + + return tags.FlattenAndSet(d, resp.Tags) +} + +func resourceArmFunctionAppSlotDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Web.AppServicesClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.FunctionAppSlotID(d.Id()) + if err != nil { + return err + } + + log.Printf("[DEBUG] Deleting Function App Slot %q (Function App %q / Resource Group %q)", id.Name, id.FunctionAppName, id.ResourceGroup) + + deleteMetrics := true + deleteEmptyServerFarm := false + resp, err := client.DeleteSlot(ctx, id.ResourceGroup, id.FunctionAppName, id.Name, &deleteMetrics, &deleteEmptyServerFarm) + if err != nil { + if !utils.ResponseWasNotFound(resp) { + return err + } + } + + return nil +} + +func getBasicFunctionAppSlotAppSettings(d *schema.ResourceData, appServiceTier, endpointSuffix string) []web.NameValuePair { + // 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" + + storageAccount := d.Get("storage_account_name").(string) + connectionString := d.Get("storage_account_access_key").(string) + 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 + if strings.EqualFold(appServiceTier, "dynamic") || strings.EqualFold(appServiceTier, "elasticpremium") { + return append(basicSettings, consumptionSettings...) + } + + return basicSettings +} + +func getFunctionAppSlotServiceTier(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 expandFunctionAppSlotAppSettings(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 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))) + } + + 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) + + 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)) + + 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 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) + + 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 flattenFunctionAppSlotSiteCredential(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 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/resource_arm_function_app_slot_test.go b/azurerm/internal/services/web/tests/resource_arm_function_app_slot_test.go new file mode 100644 index 000000000000..e62805c626ee --- /dev/null +++ b/azurerm/internal/services/web/tests/resource_arm_function_app_slot_test.go @@ -0,0 +1,2106 @@ +package tests + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMFunctionAppSlot_basic(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_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_requiresImport(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_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + ), + }, + data.RequiresImportErrorStep(testAccAzureRMFunctionAppSlot_requiresImport), + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_32Bit(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_32Bit(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.use_32_bit_worker_process", "true"), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_alwaysOn(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_alwaysOn(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.always_on", "true"), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_appSettings(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_appSettings(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "app_settings.foo", "bar"), + ), + }, + + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_clientAffinityEnabled(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_clientAffinityEnabled(data, true), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "client_affinity_enabled", "true"), + ), + }, + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_clientAffinityEnabledUpdate(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_clientAffinityEnabled(data, true), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "client_affinity_enabled", "true"), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMFunctionAppSlot_clientAffinityEnabled(data, false), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "client_affinity_enabled", "false"), + ), + }, + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_connectionStrings(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_connectionStrings(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "connection_string.3173438943.name", "First"), + resource.TestCheckResourceAttr(data.ResourceName, "connection_string.3173438943.value", "first-connection-string"), + resource.TestCheckResourceAttr(data.ResourceName, "connection_string.3173438943.type", "Custom"), + resource.TestCheckResourceAttr(data.ResourceName, "connection_string.2442860602.name", "Second"), + resource.TestCheckResourceAttr(data.ResourceName, "connection_string.2442860602.value", "some-postgresql-connection-string"), + resource.TestCheckResourceAttr(data.ResourceName, "connection_string.2442860602.type", "PostgreSQL"), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMFunctionAppSlot_connectionStringsUpdated(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "connection_string.3173438943.name", "First"), + resource.TestCheckResourceAttr(data.ResourceName, "connection_string.3173438943.value", "first-connection-string"), + resource.TestCheckResourceAttr(data.ResourceName, "connection_string.3173438943.type", "Custom"), + resource.TestCheckResourceAttr(data.ResourceName, "connection_string.2442860602.name", "Second"), + resource.TestCheckResourceAttr(data.ResourceName, "connection_string.2442860602.value", "some-postgresql-connection-string"), + resource.TestCheckResourceAttr(data.ResourceName, "connection_string.2442860602.type", "PostgreSQL"), + ), + }, + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_corsSettings(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_corsSettings(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} +func TestAccAzureRMFunctionAppSlot_authSettings(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_function_app_slot", "test") + + tenantID := os.Getenv("ARM_TENANT_ID") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMFunctionAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMFunctionAppSlot_authSettings(data, tenantID), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServiceExists(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"), + resource.TestCheckResourceAttr(data.ResourceName, "auth_settings.0.unauthenticated_client_action", "RedirectToLoginPage"), + resource.TestCheckResourceAttr(data.ResourceName, "auth_settings.0.token_refresh_extension_hours", "75"), + resource.TestCheckResourceAttr(data.ResourceName, "auth_settings.0.token_store_enabled", "true"), + resource.TestCheckResourceAttr(data.ResourceName, "auth_settings.0.additional_login_params.test_key", "test_value"), + resource.TestCheckResourceAttr(data.ResourceName, "auth_settings.0.allowed_external_redirect_urls.#", "1"), + resource.TestCheckResourceAttr(data.ResourceName, "auth_settings.0.allowed_external_redirect_urls.0", "https://terra.form"), + resource.TestCheckResourceAttr(data.ResourceName, "auth_settings.0.active_directory.0.client_id", "aadclientid"), + resource.TestCheckResourceAttr(data.ResourceName, "auth_settings.0.active_directory.0.client_secret", "aadsecret"), + resource.TestCheckResourceAttr(data.ResourceName, "auth_settings.0.active_directory.0.allowed_audiences.#", "1"), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_enabled(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_enabled(data, false), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "enabled", "false"), + ), + }, + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_enabledUpdate(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_enabled(data, false), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "enabled", "false"), + ), + }, + { + Config: testAccAzureRMFunctionAppSlot_enabled(data, true), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "enabled", "true"), + ), + }, + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_httpsOnly(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_httpsOnly(data, true), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "https_only", "true"), + ), + }, + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_httpsOnlyUpdate(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_httpsOnly(data, true), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "https_only", "true"), + ), + }, + { + Config: testAccAzureRMFunctionAppSlot_httpsOnly(data, false), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "https_only", "false"), + ), + }, + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_http2Enabled(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_http2Enabled(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.http2_enabled", "true"), + ), + }, + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_oneIpRestriction(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_oneIpRestriction(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.0.ip_address", "10.10.10.10/32"), + ), + }, + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_oneVNetSubnetIpRestriction(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_oneVNetSubnetIpRestriction(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_zeroedIpRestriction(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{ + { + // This configuration includes a single explicit ip_restriction + Config: testAccAzureRMFunctionAppSlot_oneIpRestriction(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.#", "1"), + ), + }, + { + // This configuration has no site_config blocks at all. + Config: testAccAzureRMFunctionAppSlot_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.#", "1"), + ), + }, + { + // This configuration explicitly sets ip_restriction to [] using attribute syntax. + Config: testAccAzureRMFunctionAppSlot_zeroedIpRestriction(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.ip_restriction.#", "0"), + ), + }, + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_manyIpRestrictions(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_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"), + ), + }, + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_tagsUpdate(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_tags(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(data.ResourceName, "tags.Hello", "World"), + ), + }, + { + Config: testAccAzureRMFunctionAppSlot_tagsUpdated(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(data.ResourceName, "tags.Hello", "World"), + resource.TestCheckResourceAttr(data.ResourceName, "tags.Terraform", "AcceptanceTests"), + ), + }, + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_updateManageServiceIdentity(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_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + ), + }, + { + Config: testAccAzureRMFunctionAppSlot_enableManageServiceIdentity(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "identity.0.type", "SystemAssigned"), + resource.TestMatchResourceAttr(data.ResourceName, "identity.0.principal_id", validate.UUIDRegExp), + resource.TestMatchResourceAttr(data.ResourceName, "identity.0.tenant_id", validate.UUIDRegExp), + ), + }, + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_updateVersion(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_version(data, "~1"), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "version", "~1"), + ), + }, + { + Config: testAccAzureRMFunctionAppSlot_version(data, "~2"), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "version", "~2"), + ), + }, + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_userAssignedIdentity(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_userAssignedIdentity(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "identity.0.type", "UserAssigned"), + resource.TestCheckResourceAttr(data.ResourceName, "identity.0.identity_ids.#", "1"), + resource.TestCheckResourceAttr(data.ResourceName, "identity.0.principal_id", ""), + resource.TestCheckResourceAttr(data.ResourceName, "identity.0.tenant_id", ""), + ), + }, + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_webSockets(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_webSockets(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.websockets_enabled", "true"), + ), + }, + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_enableManageServiceIdentity(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_enableManageServiceIdentity(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "identity.0.type", "SystemAssigned"), + resource.TestMatchResourceAttr(data.ResourceName, "identity.0.principal_id", validate.UUIDRegExp), + resource.TestMatchResourceAttr(data.ResourceName, "identity.0.tenant_id", validate.UUIDRegExp), + ), + }, + }, + }) +} + +func TestAccAzureRMFunctionAppSlot_minTls(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_minTls(data, "1.0"), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppSlotExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.min_tls_version", "1.0"), + ), + }, + { + Config: testAccAzureRMFunctionAppSlot_minTls(data, "1.1"), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "site_config.0.min_tls_version", "1.1"), + ), + }, + data.ImportStep(), + }, + }) +} + +func testCheckAzureRMFunctionAppSlotDestroy(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).Web.AppServicesClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_function_app_slot" { + continue + } + + slot := rs.Primary.Attributes["name"] + FunctionAppName := rs.Primary.Attributes["function_app_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := client.GetSlot(ctx, resourceGroup, FunctionAppName, slot) + + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return nil + } + return err + } + + return nil + } + + return nil +} + +func testCheckAzureRMFunctionAppSlotExists(slot string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).Web.AppServicesClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[slot] + if !ok { + return fmt.Errorf("Slot Not found: %q", slot) + } + + FunctionAppName := rs.Primary.Attributes["function_app_name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for Function App Slot: %q/%q", FunctionAppName, slot) + } + + resp, err := client.GetSlot(ctx, resourceGroup, FunctionAppName, slot) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Function App slot %q/%q (resource group: %q) does not exist", FunctionAppName, slot, resourceGroup) + } + + return fmt.Errorf("Bad: Get on AppServicesClient: %+v", err) + } + + return nil + } +} + +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" { + 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 +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func testAccAzureRMFunctionAppSlot_requiresImport(data acceptance.TestData) string { + template := testAccAzureRMFunctionAppSlot_basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_function_app_slot" "import" { + name = azurerm_function_app_slot.test.name + location = azurerm_function_app_slot.test.location + resource_group_name = azurerm_function_app_slot.test.resource_group_name + app_service_plan_id = azurerm_function_app_slot.test.app_service_plan_id + function_app_name = azurerm_function_app_slot.test.function_app_name + storage_account_name = azurerm_function_app_slot.test.storage_account_name + storage_account_access_key = azurerm_function_app_slot.test.storage_account_access_key +} +`, template) +} + +func testAccAzureRMFunctionAppSlot_32Bit(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 { + use_32_bit_worker_process = true + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func testAccAzureRMFunctionAppSlot_alwaysOn(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 { + always_on = true + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func testAccAzureRMFunctionAppSlot_appSettings(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 + + app_settings = { + "foo" = "bar" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func testAccAzureRMFunctionAppSlot_clientAffinityEnabled(data acceptance.TestData, clientAffinityEnabled bool) 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 + client_affinity_enabled = %t +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger, clientAffinityEnabled) +} + +func testAccAzureRMFunctionAppSlot_connectionStrings(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 + + connection_string { + name = "First" + value = "first-connection-string" + type = "Custom" + } + + connection_string { + name = "Second" + value = "some-postgresql-connection-string" + type = "PostgreSQL" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func testAccAzureRMFunctionAppSlot_connectionStringsUpdated(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 + + connection_string { + name = "Second" + value = "some-postgresql-connection-string" + type = "PostgreSQL" + } + + connection_string { + name = "First" + value = "first-connection-string" + type = "Custom" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func testAccAzureRMFunctionAppSlot_corsSettings(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 { + cors { + allowed_origins = [ + "http://www.contoso.com", + "www.contoso.com", + "contoso.com", + ] + support_credentials = true + } + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func testAccAzureRMFunctionAppSlot_authSettings(data acceptance.TestData, tenantID string) 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 = "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 +} + +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 + + auth_settings { + enabled = true + issuer = "https://sts.windows.net/%s" + runtime_version = "1.0" + unauthenticated_client_action = "RedirectToLoginPage" + token_refresh_extension_hours = 75 + token_store_enabled = true + + additional_login_params = { + test_key = "test_value" + } + + allowed_external_redirect_urls = [ + "https://terra.form", + ] + + active_directory { + client_id = "aadclientid" + client_secret = "aadsecret" + + allowed_audiences = [ + "activedirectorytokenaudiences", + ] + } + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger, tenantID) +} + +func testAccAzureRMFunctionAppSlot_enabled(data acceptance.TestData, enabled bool) 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 + enabled = %t +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger, enabled) +} + +func testAccAzureRMFunctionAppSlot_httpsOnly(data acceptance.TestData, httpsOnly bool) 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 + https_only = %t +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger, httpsOnly) +} + +func testAccAzureRMFunctionAppSlot_http2Enabled(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 { + http2_enabled = true + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func testAccAzureRMFunctionAppSlot_oneIpRestriction(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" + } + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func testAccAzureRMFunctionAppSlot_oneVNetSubnetIpRestriction(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%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_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 { + subnet_id = azurerm_subnet.test.id + } + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func testAccAzureRMFunctionAppSlot_zeroedIpRestriction(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 = [] + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func testAccAzureRMFunctionAppSlot_manyIpRestrictions(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" + } + + ip_restriction { + ip_address = "20.20.20.0/24" + } + + ip_restriction { + ip_address = "30.30.0.0/16" + } + + ip_restriction { + ip_address = "192.168.1.2/24" + } + } +} +`, 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" { + 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 + + tags = { + Hello = "World" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func testAccAzureRMFunctionAppSlot_tagsUpdated(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 + + tags = { + "Hello" = "World" + "Terraform" = "AcceptanceTests" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func testAccAzureRMFunctionAppSlot_webSockets(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 { + websockets_enabled = true + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func testAccAzureRMFunctionAppSlot_enableManageServiceIdentity(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 + + identity { + type = "SystemAssigned" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func testAccAzureRMFunctionAppSlot_version(data acceptance.TestData, version string) 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 + version = "%s" +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger, version) +} + +func testAccAzureRMFunctionAppSlot_userAssignedIdentity(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acct-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +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 + + identity { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.test.id] + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger) +} + +func testAccAzureRMFunctionAppSlot_minTls(data acceptance.TestData, tlsVersion string) 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 { + min_tls_version = "%s" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomInteger, data.RandomInteger, tlsVersion) +} + +func testAccAzureRMFunctionAppSlot_preWarmedInstanceCount(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" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + kind = "elastic" + sku { + tier = "ElasticPremium" + size = "EP1" + } +} + +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 { + pre_warmed_instance_count = "1" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger, data.RandomInteger) +} diff --git a/azurerm/internal/services/web/validate/app_service.go b/azurerm/internal/services/web/validate/app_service.go new file mode 100644 index 000000000000..71b389e716ae --- /dev/null +++ b/azurerm/internal/services/web/validate/app_service.go @@ -0,0 +1,16 @@ +package validate + +import ( + "fmt" + "regexp" +) + +func AppServiceName(v interface{}, k string) (warnings []string, errors []error) { + value := v.(string) + + if matched := regexp.MustCompile(`^[0-9a-zA-Z-]{1,60}$`).Match([]byte(value)); !matched { + errors = append(errors, fmt.Errorf("%q may only contain alphanumeric characters and dashes and up to 60 characters in length", k)) + } + + return warnings, errors +} diff --git a/azurerm/internal/services/web/validation_test.go b/azurerm/internal/services/web/validation_test.go index 212ac37d14ae..2c185af0baac 100644 --- a/azurerm/internal/services/web/validation_test.go +++ b/azurerm/internal/services/web/validation_test.go @@ -1,6 +1,10 @@ package web -import "testing" +import ( + "testing" + + webValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/web/validate" +) func TestAzureRMAppServicePlanName_validation(t *testing.T) { cases := []struct { @@ -74,7 +78,7 @@ func TestAzureRMAppServiceName_validation(t *testing.T) { } for _, tc := range cases { - _, errors := validateAppServiceName(tc.Value, "azurerm_app_service") + _, errors := webValidate.AppServiceName(tc.Value, "azurerm_app_service") if len(errors) != tc.ErrCount { t.Fatalf("Expected the App Service Name to trigger a validation error for '%s'", tc.Value) diff --git a/website/azurerm.erb b/website/azurerm.erb index 6c5a4240f2f0..a22b4bb6a98b 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -794,6 +794,10 @@
  • azurerm_function_app
  • + +
  • + azurerm_function_app_slot +
  • diff --git a/website/docs/r/function_app_slot.html.markdown b/website/docs/r/function_app_slot.html.markdown new file mode 100644 index 000000000000..8a50974e1e04 --- /dev/null +++ b/website/docs/r/function_app_slot.html.markdown @@ -0,0 +1,288 @@ +--- +subcategory: "App Service (Web Apps)" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_function_app_slot" +description: |- + Manages a Function App Deployment Slot. + +--- + +# azurerm_function_app_slot + +Manages a Function App deployment Slot. + +## Example Usage (with App Service Plan) + +```hcl +resource "azurerm_resource_group" "example" { + name = "azure-functions-test-rg" + location = "westus2" +} + +resource "azurerm_storage_account" "example" { + name = "functionsapptestsa" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_app_service_plan" "example" { + name = "azure-functions-test-service-plan" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_function_app" "example" { + name = "test-azure-functions" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + app_service_plan_id = azurerm_app_service_plan.example.id + storage_connection_string = azurerm_storage_account.example.primary_connection_string +} + +resource "azurerm_function_app_slot" "example" { + name = "test-azure-functions_slot" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + app_service_plan_id = azurerm_app_service_plan.example.id + function_app_name = azurerm_function_app.example.name + storage_connection_string = azurerm_storage_account.example.primary_connection_string +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Specifies the name of the Function App. Changing this forces a new resource to be created. + +* `resource_group_name` - (Required) The name of the resource group in which to create the Function App. + +* `location` - (Required) Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created. + +* `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 of the backend storage account which will be used by this Function App (such as the dashboard, logs). + +* `app_settings` - (Optional) A key-value pair of App Settings. + +~> **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" = ""`). + +~> **Note:** When using an App Service Plan in the `Free` or `Shared` Tiers `use_32_bit_worker_process` must be set to `true`. + +* `auth_settings` - (Optional) An `auth_settings` block as defined below. + +* `enable_builtin_logging` - (Optional) Should the built-in logging of this Function App be enabled? Defaults to `true`. + +* `connection_string` - (Optional) A `connection_string` 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). + +* `client_affinity_enabled` - (Optional) Should the Function App send session affinity cookies, which route client requests in the same session to the same instance? + +* `enabled` - (Optional) Is the Function App enabled? + +* `https_only` - (Optional) Can the Function App only be accessed via HTTPS? Defaults to `false`. + +* `version` - (Optional) The runtime version associated with the Function App. Defaults to `~1`. + +* `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`. + +* `site_config` - (Optional) A `site_config` object as defined below. + +* `identity` - (Optional) An `identity` block as defined below. + +* `tags` - (Optional) A mapping of tags to assign to the resource. + +--- + +`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. + +--- + +`site_config` supports the following: + +* `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`. + +~> **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? + +* `linux_fx_version` - (Optional) Linux App Framework and version for the AppService, e.g. `DOCKER|(golang:latest)`. + +* `http2_enabled` - (Optional) Specifies whether or not the http2 protocol should be enabled. Defaults to `false`. + +* `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. + +* `ftps_state` - (Optional) State of FTP / FTPS service for this function app. Possible values include: `AllAllowed`, `FtpsOnly` and `Disabled`. + +* `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. + +* `ip_restriction` - (Optional) A [List of objects](/docs/configuration/attr-as-blocks.html) representing ip restrictions as defined below. + +--- + +A `cors` block supports the following: + +* `allowed_origins` - (Optional) A list of origins which should be able to make cross-origin calls. `*` can be used to allow all calls. + +* `support_credentials` - (Optional) Are credentials supported? + +--- + +An `identity` block supports the following: + +* `type` - (Required) Specifies the identity type of the Function App. Possible values are `SystemAssigned` (where Azure will generate a Service Principal for you), `UserAssigned` where you can specify the Service Principal IDs in the `identity_ids` field, and `SystemAssigned, UserAssigned` which assigns both a system managed identity as well as the specified user assigned identities. + +~> **NOTE:** When `type` is set to `SystemAssigned`, The assigned `principal_id` and `tenant_id` can be retrieved after the Function App has been created. More details are available below. + +* `identity_ids` - (Optional) Specifies a list of user managed identity ids to be assigned. Required if `type` is `UserAssigned`. + +--- + +An `auth_settings` block supports the following: + +* `enabled` - (Required) Is Authentication enabled? + +* `active_directory` - (Optional) An `active_directory` block as defined below. + +* `additional_login_params` - (Optional) Login parameters to send to the OpenID Connect authorization endpoint when a user logs in. Each parameter must be in the form "key=value". + +* `allowed_external_redirect_urls` - (Optional) External URLs that can be redirected to as part of logging in or logging out of the app. + +* `default_provider` - (Optional) The default provider to use when multiple providers have been set up. Possible values are `AzureActiveDirectory`, `Facebook`, `Google`, `MicrosoftAccount` and `Twitter`. + +~> **NOTE:** When using multiple providers, the default provider must be set for settings like `unauthenticated_client_action` to work. + +* `facebook` - (Optional) A `facebook` block as defined below. + +* `google` - (Optional) A `google` block as defined below. + +* `issuer` - (Optional) Issuer URI. When using Azure Active Directory, this value is the URI of the directory tenant, e.g. https://sts.windows.net/{tenant-guid}/. + +* `microsoft` - (Optional) A `microsoft` block as defined below. + +* `runtime_version` - (Optional) The runtime version of the Authentication/Authorization module. + +* `token_refresh_extension_hours` - (Optional) The number of hours after session token expiration that a session token can be used to call the token refresh API. Defaults to 72. + +* `token_store_enabled` - (Optional) If enabled the module will durably store platform-specific security tokens that are obtained during login flows. Defaults to false. + +* `twitter` - (Optional) A `twitter` block as defined below. + +* `unauthenticated_client_action` - (Optional) The action to take when an unauthenticated client attempts to access the app. Possible values are `AllowAnonymous` and `RedirectToLoginPage`. + +--- + +An `active_directory` block supports the following: + +* `client_id` - (Required) The Client ID of this relying party application. Enables OpenIDConnection authentication with Azure Active Directory. + +* `client_secret` - (Optional) The Client Secret of this relying party application. If no secret is provided, implicit flow will be used. + +* `allowed_audiences` (Optional) Allowed audience values to consider when validating JWTs issued by Azure Active Directory. + +--- + +A `facebook` block supports the following: + +* `app_id` - (Required) The App ID of the Facebook app used for login + +* `app_secret` - (Required) The App Secret of the Facebook app used for Facebook Login. + +* `oauth_scopes` (Optional) The OAuth 2.0 scopes that will be requested as part of Facebook Login authentication. https://developers.facebook.com/docs/facebook-login + +--- + +A `google` block supports the following: + +* `client_id` - (Required) The OpenID Connect Client ID for the Google web application. + +* `client_secret` - (Required) The client secret associated with the Google web application. + +* `oauth_scopes` (Optional) The OAuth 2.0 scopes that will be requested as part of Google Sign-In authentication. https://developers.google.com/identity/sign-in/web/ + +--- + +A `microsoft` block supports the following: + +* `client_id` - (Required) The OAuth 2.0 client ID that was created for the app used for authentication. + +* `client_secret` - (Required) The OAuth 2.0 client secret that was created for the app used for authentication. + +* `oauth_scopes` (Optional) The OAuth 2.0 scopes that will be requested as part of Microsoft Account authentication. https://msdn.microsoft.com/en-us/library/dn631845.aspx + +--- + +A `ip_restriction` block supports the following: + +* `ip_address` - (Optional) The IP Address CIDR notation used for this IP Restriction. + +* `subnet_id` - (Optional) The Subnet ID used for this IP Restriction. + +-> **NOTE:** One of either `ip_address` or `subnet_id` must be specified + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the Function App + +* `default_hostname` - The default hostname associated with the Function App - such as `mysite.azurewebsites.net` + +* `outbound_ip_addresses` - A comma separated list of outbound IP addresses - such as `52.23.25.3,52.143.43.12` + +* `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`. + +* `identity` - An `identity` block as defined below, which contains the Managed Service Identity information for this App Service. + +* `site_credential` - A `site_credential` block as defined below, which contains the site-level credentials used to publish to this App Service. + +* `kind` - The Function App kind - such as `functionapp,linux,container` + +--- + +The `identity` block exports the following: + +* `principal_id` - The Principal ID for the Service Principal associated with the Managed Service Identity of this App Service. + +* `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 + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Function App Deployment Slot. +* `update` - (Defaults to 30 minutes) Used when updating the Function App Deployment Slot. +* `read` - (Defaults to 5 minutes) Used when retrieving the Function App Deployment Slot. +* `delete` - (Defaults to 30 minutes) Used when deleting the Function App Deployment Slot. + +## Import + +Function Apps Deployment Slots can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_function_app.functionapp1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1/providers/Microsoft.Web/sites/functionapp1/slots/staging +```