diff --git a/azurerm/resource_arm_app_service.go b/azurerm/resource_arm_app_service.go index 0d0e7d2ec685..0aa5fb3dfad6 100644 --- a/azurerm/resource_arm_app_service.go +++ b/azurerm/resource_arm_app_service.go @@ -1,14 +1,17 @@ package azurerm import ( + "context" "fmt" "log" "regexp" + "time" "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2018-02-01/web" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -21,6 +24,11 @@ func resourceArmAppService() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Update: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Schema: map[string]*schema.Schema{ "name": { @@ -188,6 +196,21 @@ func resourceArmAppServiceCreate(d *schema.ResourceData, meta interface{}) error log.Printf("[INFO] preparing arguments for AzureRM App Service creation.") name := d.Get("name").(string) + resGroup := d.Get("resource_group_name").(string) + + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of App Service %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_app_service", *resp.ID) + } + + // then check the name's globally unique availabilityRequest := web.ResourceNameAvailabilityRequest{ Name: utils.String(name), Type: web.CheckNameResourceTypesMicrosoftWebsites, @@ -201,7 +224,6 @@ func resourceArmAppServiceCreate(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("The name %q used for the App Service needs to be globally unique and isn't available: %s", name, *available.Message) } - resGroup := d.Get("resource_group_name").(string) location := azureRMNormalizeLocation(d.Get("location").(string)) appServicePlanId := d.Get("app_service_plan_id").(string) enabled := d.Get("enabled").(bool) @@ -236,7 +258,9 @@ func resourceArmAppServiceCreate(d *schema.ResourceData, meta interface{}) error return err } - err = createFuture.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + err = createFuture.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -289,7 +313,9 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutUpdate)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -300,7 +326,7 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error siteConfigResource := web.SiteConfigResource{ SiteConfig: &siteConfig, } - _, err := client.CreateOrUpdateConfiguration(ctx, resGroup, name, siteConfigResource) + _, err := client.CreateOrUpdateConfiguration(waitCtx, resGroup, name, siteConfigResource) if err != nil { return fmt.Errorf("Error updating Configuration for App Service %q: %+v", name, err) } @@ -317,7 +343,7 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error }, } - _, err := client.Update(ctx, resGroup, name, sitePatchResource) + _, err := client.Update(waitCtx, resGroup, name, sitePatchResource) if err != nil { return fmt.Errorf("Error updating App Service ARR Affinity setting %q: %+v", name, err) } @@ -330,7 +356,7 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error Properties: appSettings, } - _, err := client.UpdateApplicationSettings(ctx, resGroup, name, settings) + _, err := client.UpdateApplicationSettings(waitCtx, resGroup, name, settings) if err != nil { return fmt.Errorf("Error updating Application Settings for App Service %q: %+v", name, err) } @@ -343,14 +369,14 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error Properties: connectionStrings, } - _, err := client.UpdateConnectionStrings(ctx, resGroup, name, properties) + _, err := client.UpdateConnectionStrings(waitCtx, resGroup, name, properties) if err != nil { return fmt.Errorf("Error updating Connection Strings for App Service %q: %+v", name, err) } } if d.HasChange("identity") { - site, err := client.Get(ctx, resGroup, name) + site, err := client.Get(waitCtx, resGroup, name) if err != nil { return fmt.Errorf("Error getting configuration for App Service %q: %+v", name, err) } @@ -358,13 +384,13 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error appServiceIdentity := expandAzureRmAppServiceIdentity(d) site.Identity = appServiceIdentity - future, err := client.CreateOrUpdate(ctx, resGroup, name, site) + future, err := client.CreateOrUpdate(waitCtx, resGroup, name, site) if err != nil { return fmt.Errorf("Error updating Managed Service Identity for App Service %q: %+v", name, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error updating Managed Service Identity for App Service %q: %+v", name, err) @@ -491,7 +517,9 @@ func resourceArmAppServiceDelete(d *schema.ResourceData, meta interface{}) error deleteMetrics := true deleteEmptyServerFarm := false ctx := meta.(*ArmClient).StopContext - resp, err := client.Delete(ctx, resGroup, name, &deleteMetrics, &deleteEmptyServerFarm) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resGroup, name, &deleteMetrics, &deleteEmptyServerFarm) if err != nil { if !utils.ResponseWasNotFound(resp) { return err diff --git a/azurerm/resource_arm_app_service_custom_hostname_binding.go b/azurerm/resource_arm_app_service_custom_hostname_binding.go index 62ead0e706f0..5efd772b6a56 100644 --- a/azurerm/resource_arm_app_service_custom_hostname_binding.go +++ b/azurerm/resource_arm_app_service_custom_hostname_binding.go @@ -1,11 +1,14 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2018-02-01/web" "github.com/hashicorp/terraform/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -17,6 +20,10 @@ func resourceArmAppServiceCustomHostnameBinding() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 10), + Delete: schema.DefaultTimeout(time.Minute * 10), + }, Schema: map[string]*schema.Schema{ "hostname": { @@ -46,12 +53,26 @@ func resourceArmAppServiceCustomHostnameBindingCreate(d *schema.ResourceData, me appServiceName := d.Get("app_service_name").(string) hostname := d.Get("hostname").(string) + // first check if there's one in this subscription requiring import + resp, err := client.GetHostNameBinding(ctx, resourceGroup, appServiceName, hostname) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Hostname Binding %q (App Service Name %q / Resource Group %q): %+v", hostname, appServiceName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_app_service_custom_hostname_binding", *resp.ID) + } + properties := web.HostNameBinding{ HostNameBindingProperties: &web.HostNameBindingProperties{ SiteName: utils.String(appServiceName), }, } - _, err := client.CreateOrUpdateHostNameBinding(ctx, resourceGroup, appServiceName, hostname, properties) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + _, err = client.CreateOrUpdateHostNameBinding(waitCtx, resourceGroup, appServiceName, hostname, properties) if err != nil { return err } @@ -113,7 +134,9 @@ func resourceArmAppServiceCustomHostnameBindingDelete(d *schema.ResourceData, me log.Printf("[DEBUG] Deleting App Service Hostname Binding %q (App Service %q / Resource Group %q)", hostname, appServiceName, resGroup) ctx := meta.(*ArmClient).StopContext - resp, err := client.DeleteHostNameBinding(ctx, resGroup, appServiceName, hostname) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.DeleteHostNameBinding(waitCtx, resGroup, appServiceName, hostname) if err != nil { if !utils.ResponseWasNotFound(resp) { return err diff --git a/azurerm/resource_arm_app_service_plan.go b/azurerm/resource_arm_app_service_plan.go index d10e9300bb4d..eb768d5e42ea 100644 --- a/azurerm/resource_arm_app_service_plan.go +++ b/azurerm/resource_arm_app_service_plan.go @@ -1,13 +1,16 @@ package azurerm import ( + "context" "fmt" "log" "regexp" + "time" "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2018-02-01/web" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -20,6 +23,11 @@ func resourceArmAppServicePlan() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Update: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Schema: map[string]*schema.Schema{ "name": { @@ -116,6 +124,21 @@ func resourceArmAppServicePlanCreateUpdate(d *schema.ResourceData, meta interfac resGroup := d.Get("resource_group_name").(string) name := d.Get("name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of App Service Plan %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_app_service_plan", *resp.ID) + } + } + location := azureRMNormalizeLocation(d.Get("location").(string)) kind := d.Get("kind").(string) tags := d.Get("tags").(map[string]interface{}) @@ -136,7 +159,9 @@ func resourceArmAppServicePlanCreateUpdate(d *schema.ResourceData, meta interfac return err } - err = createFuture.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = createFuture.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -215,8 +240,9 @@ func resourceArmAppServicePlanDelete(d *schema.ResourceData, meta interface{}) e log.Printf("[DEBUG] Deleting app service plan %s: %s", resGroup, name) - resp, err := client.Delete(ctx, resGroup, name) - + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resGroup, name) if err != nil { if utils.ResponseWasNotFound(resp) { return nil diff --git a/azurerm/resource_arm_app_service_slot.go b/azurerm/resource_arm_app_service_slot.go index 9dbf53ab8cd2..e1611350a285 100644 --- a/azurerm/resource_arm_app_service_slot.go +++ b/azurerm/resource_arm_app_service_slot.go @@ -1,6 +1,7 @@ package azurerm import ( + "context" "fmt" "log" @@ -8,6 +9,7 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -147,13 +149,27 @@ func resourceArmAppServiceSlot() *schema.Resource { func resourceArmAppServiceSlotCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).appServicesClient + ctx := meta.(*ArmClient).StopContext log.Printf("[INFO] preparing arguments for AzureRM App Service Slot creation.") slot := d.Get("name").(string) resGroup := d.Get("resource_group_name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) appServiceName := d.Get("app_service_name").(string) + + // first check if there's one in this subscription requiring import + resp, err := client.GetSlot(ctx, resGroup, appServiceName, slot) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Slot %q (App Service %q / Resource Group %q): %+v", slot, appServiceName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_app_service_slot", *resp.ID) + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) appServicePlanId := d.Get("app_service_plan_id").(string) enabled := d.Get("enabled").(bool) httpsOnly := d.Get("https_only").(bool) @@ -181,10 +197,7 @@ func resourceArmAppServiceSlotCreate(d *schema.ResourceData, meta interface{}) e siteEnvelope.SiteProperties.ClientAffinityEnabled = utils.Bool(enabled) } - // NOTE: these seem like sensible defaults, in lieu of any better documentation. - ctx := meta.(*ArmClient).StopContext - - resp, err := client.Get(ctx, resGroup, appServiceName) + resp, err = client.Get(ctx, resGroup, appServiceName) if err != nil { if utils.ResponseWasNotFound(resp.Response) { return fmt.Errorf("[DEBUG] App Service %q (resource group %q) was not found.", appServiceName, resGroup) @@ -192,7 +205,9 @@ func resourceArmAppServiceSlotCreate(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("Error making Read request on AzureRM App Service %q: %+v", appServiceName, err) } - createFuture, err := client.CreateOrUpdateSlot(ctx, resGroup, appServiceName, siteEnvelope, slot) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + createFuture, err := client.CreateOrUpdateSlot(waitCtx, resGroup, appServiceName, siteEnvelope, slot) if err != nil { return err } @@ -253,17 +268,20 @@ func resourceArmAppServiceSlotUpdate(d *schema.ResourceData, meta interface{}) e return err } - err = createFuture.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutUpdate)) + defer cancel() + err = createFuture.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } + if d.HasChange("site_config") { // update the main configuration siteConfig := azSchema.ExpandAppServiceSiteConfig(d.Get("site_config")) siteConfigResource := web.SiteConfigResource{ SiteConfig: &siteConfig, } - _, err := client.CreateOrUpdateConfigurationSlot(ctx, resGroup, appServiceName, siteConfigResource, slot) + _, err := client.CreateOrUpdateConfigurationSlot(waitCtx, resGroup, appServiceName, siteConfigResource, slot) if err != nil { return fmt.Errorf("Error updating Configuration for App Service Slot %q/%q: %+v", appServiceName, slot, err) } @@ -277,7 +295,7 @@ func resourceArmAppServiceSlotUpdate(d *schema.ResourceData, meta interface{}) e ClientAffinityEnabled: &affinity, }, } - _, err := client.UpdateSlot(ctx, resGroup, appServiceName, sitePatchResource, slot) + _, err := client.UpdateSlot(waitCtx, resGroup, appServiceName, sitePatchResource, slot) if err != nil { return fmt.Errorf("Error updating App Service ARR Affinity setting %q: %+v", slot, err) } @@ -290,7 +308,7 @@ func resourceArmAppServiceSlotUpdate(d *schema.ResourceData, meta interface{}) e Properties: appSettings, } - _, err := client.UpdateApplicationSettingsSlot(ctx, resGroup, appServiceName, settings, slot) + _, err := client.UpdateApplicationSettingsSlot(waitCtx, resGroup, appServiceName, settings, slot) if err != nil { return fmt.Errorf("Error updating Application Settings for App Service Slot %q/%q: %+v", appServiceName, slot, err) } @@ -303,7 +321,7 @@ func resourceArmAppServiceSlotUpdate(d *schema.ResourceData, meta interface{}) e Properties: connectionStrings, } - _, err := client.UpdateConnectionStringsSlot(ctx, resGroup, appServiceName, properties, slot) + _, err := client.UpdateConnectionStringsSlot(waitCtx, resGroup, appServiceName, properties, slot) if err != nil { return fmt.Errorf("Error updating Connection Strings for App Service Slot %q/%q: %+v", appServiceName, slot, err) } @@ -403,7 +421,9 @@ func resourceArmAppServiceSlotDelete(d *schema.ResourceData, meta interface{}) e deleteMetrics := true deleteEmptyServerFarm := false ctx := meta.(*ArmClient).StopContext - resp, err := client.DeleteSlot(ctx, resGroup, appServiceName, slot, &deleteMetrics, &deleteEmptyServerFarm) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.DeleteSlot(waitCtx, resGroup, appServiceName, slot, &deleteMetrics, &deleteEmptyServerFarm) if err != nil { if !utils.ResponseWasNotFound(resp) { return err