diff --git a/azurerm/config.go b/azurerm/config.go index 3b107b149f70..672547ba630b 100644 --- a/azurerm/config.go +++ b/azurerm/config.go @@ -8,7 +8,6 @@ import ( "net/http/httputil" "os" "sync" - "time" appinsights "github.com/Azure/azure-sdk-for-go/services/appinsights/mgmt/2015-05-01/insights" "github.com/Azure/azure-sdk-for-go/services/automation/mgmt/2015-10-31/automation" @@ -264,7 +263,6 @@ func (c *ArmClient) configureClient(client *autorest.Client, auth autorest.Autho client.Authorizer = auth client.Sender = autorest.CreateSender(withRequestLogging()) client.SkipResourceProviderRegistration = c.skipProviderRegistration - client.PollingDuration = 60 * time.Minute } func withRequestLogging() autorest.SendDecorator { diff --git a/azurerm/data_source_scheduler_job_collection_test.go b/azurerm/data_source_scheduler_job_collection_test.go index aebffec1377c..1a295fc65181 100644 --- a/azurerm/data_source_scheduler_job_collection_test.go +++ b/azurerm/data_source_scheduler_job_collection_test.go @@ -41,6 +41,7 @@ func TestAccDataSourceAzureRMSchedulerJobCollection_complete(t *testing.T) { } func testAccDataSourceSchedulerJobCollection_basic(rInt int, location string) string { + template := testAccAzureRMSchedulerJobCollection_basic(rInt, location) return fmt.Sprintf(` %s @@ -48,10 +49,11 @@ data "azurerm_scheduler_job_collection" "test" { name = "${azurerm_scheduler_job_collection.test.name}" resource_group_name = "${azurerm_resource_group.test.name}" } -`, testAccAzureRMSchedulerJobCollection_basic(rInt, location, "")) +`, template) } func testAccDataSourceSchedulerJobCollection_complete(rInt int, location string) string { + template := testAccAzureRMSchedulerJobCollection_complete(rInt, location) return fmt.Sprintf(` %s @@ -59,5 +61,5 @@ data "azurerm_scheduler_job_collection" "test" { name = "${azurerm_scheduler_job_collection.test.name}" resource_group_name = "${azurerm_resource_group.test.name}" } -`, testAccAzureRMSchedulerJobCollection_complete(rInt, location)) +`, template) } diff --git a/azurerm/express_route_circuit.go b/azurerm/express_route_circuit.go deleted file mode 100644 index 1dcf263d4590..000000000000 --- a/azurerm/express_route_circuit.go +++ /dev/null @@ -1,40 +0,0 @@ -package azurerm - -import ( - "fmt" - - "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" -) - -func extractResourceGroupAndErcName(resourceId string) (resourceGroup string, name string, err error) { - id, err := parseAzureResourceID(resourceId) - - if err != nil { - return "", "", err - } - resourceGroup = id.ResourceGroup - name = id.Path["expressRouteCircuits"] - - return -} - -func retrieveErcByResourceId(resourceId string, meta interface{}) (erc *network.ExpressRouteCircuit, resourceGroup string, e error) { - ercClient := meta.(*ArmClient).expressRouteCircuitClient - ctx := meta.(*ArmClient).StopContext - - resGroup, name, err := extractResourceGroupAndErcName(resourceId) - if err != nil { - return nil, "", fmt.Errorf("Error Parsing Azure Resource ID -: %+v", err) - } - - resp, err := ercClient.Get(ctx, resGroup, name) - if err != nil { - if utils.ResponseWasNotFound(resp.Response) { - return nil, "", nil - } - return nil, "", fmt.Errorf("Error making Read request on Express Route Circuit %s: %+v", name, err) - } - - return &resp, resGroup, nil -} diff --git a/azurerm/helpers/tf/errors.go b/azurerm/helpers/tf/errors.go new file mode 100644 index 000000000000..1a2d7bcaf792 --- /dev/null +++ b/azurerm/helpers/tf/errors.go @@ -0,0 +1,8 @@ +package tf + +import "fmt" + +func ImportAsExistsError(resourceName, id string) error { + msg := "A resource with the ID %q already exists - to be managed via Terraform this resource needs to be imported into the State. Please see the resource documentation for %q for more information." + return fmt.Errorf(msg, id, resourceName) +} diff --git a/azurerm/helpers/tf/timeouts.go b/azurerm/helpers/tf/timeouts.go new file mode 100644 index 000000000000..30418943d507 --- /dev/null +++ b/azurerm/helpers/tf/timeouts.go @@ -0,0 +1,11 @@ +package tf + +import "github.com/hashicorp/terraform/helper/schema" + +func TimeoutForCreateUpdate(d *schema.ResourceData) string { + if d.IsNewResource() { + return schema.TimeoutCreate + } + + return schema.TimeoutUpdate +} diff --git a/azurerm/import_arm_app_service_hostname_binding_test.go b/azurerm/import_arm_app_service_hostname_binding_test.go deleted file mode 100644 index 9e51fcf61401..000000000000 --- a/azurerm/import_arm_app_service_hostname_binding_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package azurerm - -import ( - "os" - "testing" - - "github.com/hashicorp/terraform/helper/acctest" - "github.com/hashicorp/terraform/helper/resource" -) - -func TestAccAzureRMAppServiceCustomHostnameBinding(t *testing.T) { - // NOTE: this is a combined test rather than separate split out tests due to - // the app service name being shared (so the tests don't conflict with each other) - testCases := map[string]map[string]func(t *testing.T){ - "basic": { - "basic": testAccAzureRMAppServiceCustomHostnameBinding_basic, - "import": testAccAzureRMAppServiceCustomHostnameBinding_import, - }, - } - - for group, m := range testCases { - m := m - t.Run(group, func(t *testing.T) { - for name, tc := range m { - tc := tc - t.Run(name, func(t *testing.T) { - tc(t) - }) - } - }) - } -} - -func testAccAzureRMAppServiceCustomHostnameBinding_import(t *testing.T) { - appServiceEnvVariable := "ARM_TEST_APP_SERVICE" - appServiceEnv := os.Getenv(appServiceEnvVariable) - if appServiceEnv == "" { - t.Skipf("Skipping as %q is not specified", appServiceEnvVariable) - } - - domainEnvVariable := "ARM_TEST_DOMAIN" - domainEnv := os.Getenv(domainEnvVariable) - if domainEnv == "" { - t.Skipf("Skipping as %q is not specified", domainEnvVariable) - } - - resourceName := "azurerm_app_service_custom_hostname_binding.test" - - ri := acctest.RandInt() - location := testLocation() - config := testAccAzureRMAppServiceCustomHostnameBinding_basicConfig(ri, location, appServiceEnv, domainEnv) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testCheckAzureRMAppServiceCustomHostnameBindingDestroy, - Steps: []resource.TestStep{ - { - Config: config, - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} diff --git a/azurerm/loadbalancer.go b/azurerm/loadbalancer.go index 4b37055080c7..79295c1b0109 100644 --- a/azurerm/loadbalancer.go +++ b/azurerm/loadbalancer.go @@ -1,14 +1,12 @@ package azurerm import ( - "context" "fmt" "net/http" "regexp" "strings" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" - "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" ) @@ -43,20 +41,6 @@ func retrieveLoadBalancerById(loadBalancerId string, meta interface{}) (*network return &resp, true, nil } -func findLoadBalancerBackEndAddressPoolByName(lb *network.LoadBalancer, name string) (*network.BackendAddressPool, int, bool) { - if lb == nil || lb.LoadBalancerPropertiesFormat == nil || lb.LoadBalancerPropertiesFormat.BackendAddressPools == nil { - return nil, -1, false - } - - for i, apc := range *lb.LoadBalancerPropertiesFormat.BackendAddressPools { - if apc.Name != nil && *apc.Name == name { - return &apc, i, true - } - } - - return nil, -1, false -} - func findLoadBalancerFrontEndIpConfigurationByName(lb *network.LoadBalancer, name string) (*network.FrontendIPConfiguration, int, bool) { if lb == nil || lb.LoadBalancerPropertiesFormat == nil || lb.LoadBalancerPropertiesFormat.FrontendIPConfigurations == nil { return nil, -1, false @@ -71,81 +55,6 @@ func findLoadBalancerFrontEndIpConfigurationByName(lb *network.LoadBalancer, nam return nil, -1, false } -func findLoadBalancerRuleByName(lb *network.LoadBalancer, name string) (*network.LoadBalancingRule, int, bool) { - if lb == nil || lb.LoadBalancerPropertiesFormat == nil || lb.LoadBalancerPropertiesFormat.LoadBalancingRules == nil { - return nil, -1, false - } - - for i, lbr := range *lb.LoadBalancerPropertiesFormat.LoadBalancingRules { - if lbr.Name != nil && *lbr.Name == name { - return &lbr, i, true - } - } - - return nil, -1, false -} - -func findLoadBalancerNatRuleByName(lb *network.LoadBalancer, name string) (*network.InboundNatRule, int, bool) { - if lb == nil || lb.LoadBalancerPropertiesFormat == nil || lb.LoadBalancerPropertiesFormat.InboundNatRules == nil { - return nil, -1, false - } - - for i, nr := range *lb.LoadBalancerPropertiesFormat.InboundNatRules { - if nr.Name != nil && *nr.Name == name { - return &nr, i, true - } - } - - return nil, -1, false -} - -func findLoadBalancerNatPoolByName(lb *network.LoadBalancer, name string) (*network.InboundNatPool, int, bool) { - if lb == nil || lb.LoadBalancerPropertiesFormat == nil || lb.LoadBalancerPropertiesFormat.InboundNatPools == nil { - return nil, -1, false - } - - for i, np := range *lb.LoadBalancerPropertiesFormat.InboundNatPools { - if np.Name != nil && *np.Name == name { - return &np, i, true - } - } - - return nil, -1, false -} - -func findLoadBalancerProbeByName(lb *network.LoadBalancer, name string) (*network.Probe, int, bool) { - if lb == nil || lb.LoadBalancerPropertiesFormat == nil || lb.LoadBalancerPropertiesFormat.Probes == nil { - return nil, -1, false - } - - for i, p := range *lb.LoadBalancerPropertiesFormat.Probes { - if p.Name != nil && *p.Name == name { - return &p, i, true - } - } - - return nil, -1, false -} - -func loadbalancerStateRefreshFunc(ctx context.Context, client network.LoadBalancersClient, resourceGroupName string, loadbalancer string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - res, err := client.Get(ctx, resourceGroupName, loadbalancer, "") - if err != nil { - return nil, "", fmt.Errorf("Error issuing read request in loadbalancerStateRefreshFunc to Azure ARM for Load Balancer '%s' (RG: '%s'): %s", loadbalancer, resourceGroupName, err) - } - - return res, *res.LoadBalancerPropertiesFormat.ProvisioningState, nil - } -} - -func validateLoadBalancerPrivateIpAddressAllocation(v interface{}, k string) (ws []string, errors []error) { - value := strings.ToLower(v.(string)) - if value != "static" && value != "dynamic" { - errors = append(errors, fmt.Errorf("LoadBalancer Allocations can only be Static or Dynamic")) - } - return -} - // sets the loadbalancer_id in the ResourceData from the sub resources full id func loadBalancerSubResourceStateImporter(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { r, err := regexp.Compile(`.+\/loadBalancers\/.+?\/`) diff --git a/azurerm/logic_apps.go b/azurerm/logic_apps.go index ff7811616185..7605161fd950 100644 --- a/azurerm/logic_apps.go +++ b/azurerm/logic_apps.go @@ -1,25 +1,27 @@ package azurerm import ( + "context" "fmt" "log" "github.com/Azure/azure-sdk-for-go/services/logic/mgmt/2016-06-01/logic" "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" ) // NOTE: this file is not a recommended way of developing Terraform resources; this exists to work around the fact that this API is dynamic (by it's nature) -func resourceLogicAppActionUpdate(d *schema.ResourceData, meta interface{}, logicAppId string, name string, vals map[string]interface{}) error { - return resourceLogicAppComponentUpdate(d, meta, "Action", "actions", logicAppId, name, vals) +func resourceLogicAppActionUpdate(d *schema.ResourceData, meta interface{}, logicAppId string, name string, vals map[string]interface{}, resourceName string) error { + return resourceLogicAppComponentUpdate(d, meta, "Action", "actions", logicAppId, name, vals, resourceName) } -func resourceLogicAppTriggerUpdate(d *schema.ResourceData, meta interface{}, logicAppId string, name string, vals map[string]interface{}) error { - return resourceLogicAppComponentUpdate(d, meta, "Trigger", "triggers", logicAppId, name, vals) +func resourceLogicAppTriggerUpdate(d *schema.ResourceData, meta interface{}, logicAppId string, name string, vals map[string]interface{}, resourceName string) error { + return resourceLogicAppComponentUpdate(d, meta, "Trigger", "triggers", logicAppId, name, vals, resourceName) } -func resourceLogicAppComponentUpdate(d *schema.ResourceData, meta interface{}, kind string, propertyName string, logicAppId string, name string, vals map[string]interface{}) error { +func resourceLogicAppComponentUpdate(d *schema.ResourceData, meta interface{}, kind string, propertyName string, logicAppId string, name string, vals map[string]interface{}, resourceName string) error { client := meta.(*ArmClient).logicWorkflowsClient ctx := meta.(*ArmClient).StopContext @@ -56,6 +58,14 @@ func resourceLogicAppComponentUpdate(d *schema.ResourceData, meta interface{}, k definition := read.WorkflowProperties.Definition.(map[string]interface{}) vs := definition[propertyName].(map[string]interface{}) + + idString := fmt.Sprintf("%s/%s/%s", *read.ID, propertyName, name) + if d.IsNewResource() { + if _, exists := vs[name]; exists { + return tf.ImportAsExistsError(resourceName, idString) + } + } + vs[name] = vals definition[propertyName] = vs @@ -68,13 +78,15 @@ func resourceLogicAppComponentUpdate(d *schema.ResourceData, meta interface{}, k Tags: read.Tags, } - _, err = client.CreateOrUpdate(ctx, resourceGroup, logicAppName, properties) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err = client.CreateOrUpdate(waitCtx, resourceGroup, logicAppName, properties) if err != nil { return fmt.Errorf("Error updating Logic App Workspace %q (Resource Group %q) for %s %q: %+v", logicAppName, resourceGroup, kind, name, err) } if d.IsNewResource() { - d.SetId(fmt.Sprintf("%s/%s/%s", *read.ID, propertyName, name)) + d.SetId(idString) } return nil @@ -130,7 +142,9 @@ func resourceLogicAppComponentRemove(d *schema.ResourceData, meta interface{}, k Tags: read.Tags, } - _, err = client.CreateOrUpdate(ctx, resourceGroup, logicAppName, properties) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + _, err = client.CreateOrUpdate(waitCtx, resourceGroup, logicAppName, properties) if err != nil { return fmt.Errorf("Error removing %s %q from Logic App Workspace %q (Resource Group %q): %+v", kind, name, logicAppName, resourceGroup, err) } diff --git a/azurerm/provider.go b/azurerm/provider.go index 3b2908667350..6bf83a7e167b 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -9,6 +9,7 @@ import ( "log" "strings" "sync" + "time" "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2017-05-10/resources" "github.com/Azure/go-autorest/autorest/adal" @@ -245,10 +246,10 @@ func Provider() terraform.ResourceProvider { "azurerm_snapshot": resourceArmSnapshot(), "azurerm_scheduler_job": resourceArmSchedulerJob(), "azurerm_scheduler_job_collection": resourceArmSchedulerJobCollection(), + "azurerm_sql_active_directory_administrator": resourceArmSqlAdministrator(), "azurerm_sql_database": resourceArmSqlDatabase(), "azurerm_sql_elasticpool": resourceArmSqlElasticPool(), "azurerm_sql_firewall_rule": resourceArmSqlFirewallRule(), - "azurerm_sql_active_directory_administrator": resourceArmSqlAdministrator(), "azurerm_sql_server": resourceArmSqlServer(), "azurerm_sql_virtual_network_rule": resourceArmSqlVirtualNetworkRule(), "azurerm_storage_account": resourceArmStorageAccount(), @@ -331,7 +332,10 @@ func providerConfigure(p *schema.Provider) schema.ConfigureFunc { // replaces the context between tests p.MetaReset = func() error { - client.StopContext = p.StopContext() + // TODO: is this the right place for this? + timeout := 2 * time.Hour + ctx, _ := context.WithTimeout(p.StopContext(), timeout) + client.StopContext = ctx return nil } diff --git a/azurerm/provider_test.go b/azurerm/provider_test.go index 4c8fc5ff3d8c..b4b560816edd 100644 --- a/azurerm/provider_test.go +++ b/azurerm/provider_test.go @@ -3,6 +3,7 @@ package azurerm import ( "fmt" "os" + "regexp" "testing" "github.com/Azure/go-autorest/autorest/azure" @@ -68,6 +69,11 @@ func testArmEnvironmentName() string { return envName } +func testRequiresImportError(resourceName string) *regexp.Regexp { + message := "to be managed via Terraform this resource needs to be imported into the State. Please see the resource documentation for %q for more information." + return regexp.MustCompile(fmt.Sprintf(message, resourceName)) +} + func testArmEnvironment() (*azure.Environment, error) { envName := testArmEnvironmentName() diff --git a/azurerm/resource_arm_app_service.go b/azurerm/resource_arm_app_service.go index c09f632ceb03..aabac134a9c5 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" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "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_active_slot.go b/azurerm/resource_arm_app_service_active_slot.go index b621c180a117..357fadad82dd 100644 --- a/azurerm/resource_arm_app_service_active_slot.go +++ b/azurerm/resource_arm_app_service_active_slot.go @@ -1,23 +1,32 @@ 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" ) func resourceArmAppServiceActiveSlot() *schema.Resource { return &schema.Resource{ - Create: resourceArmAppServiceActiveSlotCreate, + Create: resourceArmAppServiceActiveSlotCreateUpdate, Read: resourceArmAppServiceActiveSlotRead, - Update: resourceArmAppServiceActiveSlotCreate, + Update: resourceArmAppServiceActiveSlotCreateUpdate, Delete: resourceArmAppServiceActiveSlotDelete, 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{ "resource_group_name": resourceGroupNameSchema(), @@ -36,7 +45,7 @@ func resourceArmAppServiceActiveSlot() *schema.Resource { } } -func resourceArmAppServiceActiveSlotCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmAppServiceActiveSlotCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).appServicesClient ctx := meta.(*ArmClient).StopContext @@ -70,7 +79,10 @@ func resourceArmAppServiceActiveSlotCreate(d *schema.ResourceData, meta interfac if err != nil { return fmt.Errorf("Error swapping App Service Slot %q/%q: %+v", appServiceName, targetSlot, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error swapping App Service Slot %q/%q: %+v", appServiceName, targetSlot, 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_custom_hostname_binding_test.go b/azurerm/resource_arm_app_service_custom_hostname_binding_test.go index 7ab2b0c6fbbb..f9da674ce28d 100644 --- a/azurerm/resource_arm_app_service_custom_hostname_binding_test.go +++ b/azurerm/resource_arm_app_service_custom_hostname_binding_test.go @@ -12,23 +12,39 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) -func testAccAzureRMAppServiceCustomHostnameBinding_basic(t *testing.T) { - appServiceEnvVariable := "ARM_TEST_APP_SERVICE" - appServiceEnv := os.Getenv(appServiceEnvVariable) - if appServiceEnv == "" { - t.Skipf("Skipping as %q is not specified", appServiceEnvVariable) - } +func TestAccAzureRMAppServiceCustomHostnameBinding_basic(t *testing.T) { + appServiceEnv, domainEnv := testAccAzureRMAppServiceCustomHostnameBinding_check(t) - domainEnvVariable := "ARM_TEST_DOMAIN" - domainEnv := os.Getenv(domainEnvVariable) - if domainEnv == "" { - t.Skipf("Skipping as %q is not specified", domainEnvVariable) - } + resourceName := "azurerm_app_service_custom_hostname_binding.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAppServiceCustomHostnameBindingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAppServiceCustomHostnameBinding_basic(ri, location, appServiceEnv, domainEnv), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServiceCustomHostnameBindingExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMAppServiceCustomHostnameBinding_requiresImport(t *testing.T) { + appServiceEnv, domainEnv := testAccAzureRMAppServiceCustomHostnameBinding_check(t) resourceName := "azurerm_app_service_custom_hostname_binding.test" ri := acctest.RandInt() location := testLocation() - config := testAccAzureRMAppServiceCustomHostnameBinding_basicConfig(ri, location, appServiceEnv, domainEnv) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -36,15 +52,37 @@ func testAccAzureRMAppServiceCustomHostnameBinding_basic(t *testing.T) { CheckDestroy: testCheckAzureRMAppServiceCustomHostnameBindingDestroy, Steps: []resource.TestStep{ { - Config: config, + Config: testAccAzureRMAppServiceCustomHostnameBinding_basic(ri, location, appServiceEnv, domainEnv), Check: resource.ComposeTestCheckFunc( testCheckAzureRMAppServiceCustomHostnameBindingExists(resourceName), ), }, + { + Config: testAccAzureRMAppServiceCustomHostnameBinding_requiresImport(ri, location, appServiceEnv, domainEnv), + ExpectError: testRequiresImportError("azurerm_app_service_custom_hostname_binding"), + }, }, }) } +func testAccAzureRMAppServiceCustomHostnameBinding_check(t *testing.T) (string, string) { + appServiceEnvVariable := "ARM_TEST_APP_SERVICE" + appServiceEnv := os.Getenv(appServiceEnvVariable) + if appServiceEnv == "" { + t.Skipf("Skipping as %q is not specified", appServiceEnvVariable) + return "", "" + } + + domainEnvVariable := "ARM_TEST_DOMAIN" + domainEnv := os.Getenv(domainEnvVariable) + if domainEnv == "" { + t.Skipf("Skipping as %q is not specified", domainEnvVariable) + return "", "" + } + + return appServiceEnv, domainEnv +} + func testCheckAzureRMAppServiceCustomHostnameBindingDestroy(s *terraform.State) error { client := testAccProvider.Meta().(*ArmClient).appServicesClient @@ -100,7 +138,7 @@ func testCheckAzureRMAppServiceCustomHostnameBindingExists(name string) resource } } -func testAccAzureRMAppServiceCustomHostnameBinding_basicConfig(rInt int, location string, appServiceName string, domain string) string { +func testAccAzureRMAppServiceCustomHostnameBinding_basic(rInt int, location string, appServiceName string, domain string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { name = "acctestRG-%d" @@ -132,3 +170,16 @@ resource "azurerm_app_service_custom_hostname_binding" "test" { } `, rInt, location, rInt, appServiceName, domain) } + +func testAccAzureRMAppServiceCustomHostnameBinding_requiresImport(rInt int, location string, appServiceName string, domain string) string { + template := testAccAzureRMAppServiceCustomHostnameBinding_basic(rInt, location, appServiceName, domain) + return fmt.Sprintf(` +%s + +resource "azurerm_app_service_custom_hostname_binding" "import" { + hostname = "${azurerm_app_service_custom_hostname_binding.test.hostname}" + app_service_name = "${azurerm_app_service_custom_hostname_binding.test.app_service_name}" + resource_group_name = "${azurerm_app_service_custom_hostname_binding.test.resource_group_name}" +} +`, template) +} 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_plan_test.go b/azurerm/resource_arm_app_service_plan_test.go index 08745d4080e0..44e4c0ed3019 100644 --- a/azurerm/resource_arm_app_service_plan_test.go +++ b/azurerm/resource_arm_app_service_plan_test.go @@ -70,6 +70,7 @@ func TestAccAzureRMAppServicePlan_basicWindows(t *testing.T) { } func TestAccAzureRMAppServicePlan_basicLinux(t *testing.T) { + resourceName := "azurerm_app_service_plan.test" ri := acctest.RandInt() config := testAccAzureRMAppServicePlan_basicLinux(ri, testLocation()) @@ -80,10 +81,33 @@ func TestAccAzureRMAppServicePlan_basicLinux(t *testing.T) { Steps: []resource.TestStep{ { Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServicePlanExists(resourceName), + ), + }, + }, + }) +} + +func TestAccAzureRMAppServicePlan_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAppServicePlanDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAppServicePlan_basicLinux(ri, location), Check: resource.ComposeTestCheckFunc( testCheckAzureRMAppServicePlanExists("azurerm_app_service_plan.test"), ), }, + { + Config: testAccAzureRMAppServicePlan_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_app_service_plan"), + }, }, }) } @@ -301,6 +325,29 @@ resource "azurerm_app_service_plan" "test" { `, rInt, location, rInt) } +func testAccAzureRMAppServicePlan_requiresImport(rInt int, location string) string { + template := testAccAzureRMAppServicePlan_basicLinux(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_app_service_plan" "import" { + name = "${azurerm_app_service_plan.test.name}" + location = "${azurerm_app_service_plan.test.location}" + resource_group_name = "${azurerm_app_service_plan.test.resource_group_name}" + kind = "${azurerm_app_service_plan.test.kind}" + + sku { + tier = "Basic" + size = "B1" + } + + properties { + reserved = true + } +} +`, template) +} + func testAccAzureRMAppServicePlan_standardWindows(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_app_service_slot.go b/azurerm/resource_arm_app_service_slot.go index 294a6caae82c..beda5bab90bb 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" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "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 := azure.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 diff --git a/azurerm/resource_arm_app_service_slot_test.go b/azurerm/resource_arm_app_service_slot_test.go index 742a12062b73..4ed5b6eaf1cc 100644 --- a/azurerm/resource_arm_app_service_slot_test.go +++ b/azurerm/resource_arm_app_service_slot_test.go @@ -32,6 +32,30 @@ func TestAccAzureRMAppServiceSlot_basic(t *testing.T) { }) } +func TestAccAzureRMAppServiceSlot_requiresImport(t *testing.T) { + resourceName := "azurerm_app_service_slot.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAppServiceSlotDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAppServiceSlot_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServiceSlotExists(resourceName), + ), + }, + { + Config: testAccAzureRMAppServiceSlot_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_app_service_slot"), + }, + }, + }) +} + func TestAccAzureRMAppServiceSlot_32Bit(t *testing.T) { resourceName := "azurerm_app_service_slot.test" ri := acctest.RandInt() @@ -857,6 +881,21 @@ resource "azurerm_app_service_slot" "test" { `, rInt, location, rInt, rInt, rInt) } +func testAccAzureRMAppServiceSlot_requiresImport(rInt int, location string) string { + template := testAccAzureRMAppServiceSlot_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_app_service_slot" "import" { + name = "${azurerm_app_service_slot.test.name}" + location = "${azurerm_app_service_slot.test.location}" + resource_group_name = "${azurerm_app_service_slot.test.resource_group_name}" + app_service_plan_id = "${azurerm_app_service_slot.test.app_service_plan_id}" + app_service_name = "${azurerm_app_service_slot.test.app_service_name}" +} +`, template) +} + func testAccAzureRMAppServiceSlot_32Bit(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_app_service_test.go b/azurerm/resource_arm_app_service_test.go index d9b9fb37cbca..3f9b6d496856 100644 --- a/azurerm/resource_arm_app_service_test.go +++ b/azurerm/resource_arm_app_service_test.go @@ -71,6 +71,30 @@ func TestAccAzureRMAppService_basic(t *testing.T) { }) } +func TestAccAzureRMAppService_requiresImport(t *testing.T) { + resourceName := "azurerm_app_service.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAppServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAppService_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServiceExists(resourceName), + ), + }, + { + Config: testAccAzureRMAppService_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_app_service"), + }, + }, + }) +} + func TestAccAzureRMAppService_freeTier(t *testing.T) { resourceName := "azurerm_app_service.test" ri := acctest.RandInt() @@ -992,6 +1016,20 @@ resource "azurerm_app_service" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMAppService_requiresImport(rInt int, location string) string { + template := testAccAzureRMAppService_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_app_service" "import" { + name = "${azurerm_app_service.test.name}" + location = "${azurerm_app_service.test.location}" + resource_group_name = "${azurerm_app_service.test.resource_group_name}" + app_service_plan_id = "${azurerm_app_service.test.app_service_plan_id}" +} +`, template) +} + func testAccAzureRMAppService_freeTier(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_application_gateway.go b/azurerm/resource_arm_application_gateway.go index 5a52c00f05a1..277e88d663bb 100644 --- a/azurerm/resource_arm_application_gateway.go +++ b/azurerm/resource_arm_application_gateway.go @@ -2,14 +2,17 @@ package azurerm import ( "bytes" + "context" "fmt" "log" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" "github.com/hashicorp/terraform/helper/hashcode" "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" ) @@ -22,6 +25,11 @@ func resourceArmApplicationGateway() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Hour * 1), + Update: schema.DefaultTimeout(time.Hour * 1), + Delete: schema.DefaultTimeout(time.Hour * 1), + }, Schema: map[string]*schema.Schema{ "name": { @@ -711,11 +719,26 @@ func resourceArmApplicationGatewayCreateUpdate(d *schema.ResourceData, meta inte client := armClient.applicationGatewayClient ctx := armClient.StopContext - log.Printf("[INFO] preparing arguments for AzureRM ApplicationGateway creation.") + log.Printf("[INFO] preparing arguments for AzureRM Application Gateway creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_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 Application Gateway %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_application_gateway", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) tags := d.Get("tags").(map[string]interface{}) // Gateway ID is needed to link sub-resources together in expand functions @@ -754,7 +777,9 @@ func resourceArmApplicationGatewayCreateUpdate(d *schema.ResourceData, meta inte return fmt.Errorf("Error Creating/Updating ApplicationGateway %q (Resource Group %q): %+v", name, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error Creating/Updating ApplicationGateway %q (Resource Group %q): %+v", name, resGroup, err) } @@ -856,12 +881,13 @@ func resourceArmApplicationGatewayDelete(d *schema.ResourceData, meta interface{ return fmt.Errorf("Error deleting for AppGateway %q (Resource Group %q): %+v", name, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for deletion of AppGateway %q (Resource Group %q): %+v", name, resGroup, err) } - d.SetId("") return nil } diff --git a/azurerm/resource_arm_application_gateway_test.go b/azurerm/resource_arm_application_gateway_test.go index 420be2240628..caabd9276f84 100644 --- a/azurerm/resource_arm_application_gateway_test.go +++ b/azurerm/resource_arm_application_gateway_test.go @@ -93,6 +93,31 @@ func TestAccAzureRMApplicationGateway_basic_base(t *testing.T) { }) } +func TestAccAzureRMApplicationGateway_requiresImport(t *testing.T) { + resourceName := "azurerm_application_gateway.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMApplicationGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMApplicationGateway_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMApplicationGatewayExists(resourceName), + testCheckAzureRMApplicationGatewaySslCertificateAssigned(resourceName, "ssl-1"), + ), + }, + { + Config: testAccAzureRMApplicationGateway_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_application_gateway"), + }, + }, + }) +} + func TestAccAzureRMApplicationGateway_basic_changeSslCert(t *testing.T) { resourceName := "azurerm_application_gateway.test" ri := acctest.RandInt() @@ -599,6 +624,166 @@ resource "azurerm_application_gateway" "test" { `, rInt, location, rInt, rInt, rInt, rInt) } +func testAccAzureRMApplicationGateway_requiresImport(rInt int, location string) string { + template := testAccAzureRMApplicationGateway_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_application_gateway" "import" { + name = "${azurerm_application_gateway.test.name}" + location = "${azurerm_application_gateway.test.location}" + resource_group_name = "${azurerm_application_gateway.test.name}" + + sku { + name = "Standard_Medium" + tier = "Standard" + capacity = 1 + } + + disabled_ssl_protocols = [ + "TLSv1_0", + ] + + gateway_ip_configuration { + # id = computed + name = "gw-ip-config1" + subnet_id = "${azurerm_subnet.test.id}" + } + + frontend_ip_configuration { + # id = computed + name = "ip-config-public" + public_ip_address_id = "${azurerm_public_ip.test.id}" + } + + frontend_ip_configuration { + # id = computed + name = "ip-config-private" + subnet_id = "${azurerm_subnet.test.id}" + + # private_ip_address = computed + private_ip_address_allocation = "Dynamic" + } + + frontend_port { + # id = computed + name = "port-8080" + port = 8080 + } + + backend_address_pool { + # id = computed + name = "pool-1" + + fqdn_list = [ + "terraform.io", + ] + } + + backend_http_settings { + # id = computed + name = "backend-http-1" + port = 8010 + protocol = "Https" + cookie_based_affinity = "Enabled" + request_timeout = 30 + + # probe_id = computed + probe_name = "probe-1" + } + + http_listener { + # id = computed + name = "listener-1" + + # frontend_ip_configuration_id = computed + frontend_ip_configuration_name = "ip-config-public" + + # frontend_ip_port_id = computed + frontend_port_name = "port-8080" + protocol = "Http" + } + + http_listener { + name = "listener-2" + frontend_ip_configuration_name = "ip-config-public" + frontend_port_name = "port-8080" + protocol = "Https" + + # ssl_certificate_id = computed + ssl_certificate_name = "ssl-1" + host_name = "terraform.io" + require_sni = true + } + + probe { + # id = computed + name = "probe-1" + protocol = "Https" + path = "/test" + host = "azure.com" + timeout = 120 + interval = 300 + unhealthy_threshold = 8 + } + + url_path_map { + # id = computed + name = "path-map-1" + default_backend_address_pool_name = "pool-1" + default_backend_http_settings_name = "backend-http-1" + + path_rule { + # id = computed + name = "path-rule-1" + backend_address_pool_name = "pool-1" + backend_http_settings_name = "backend-http-1" + + paths = [ + "/test", + ] + } + } + + request_routing_rule { + # id = computed + name = "rule-basic-1" + rule_type = "Basic" + + # http_listener_id = computed + http_listener_name = "listener-1" + + # backend_address_pool_id = computed + backend_address_pool_name = "pool-1" + + # backend_http_settings_id = computed + backend_http_settings_name = "backend-http-1" + } + + request_routing_rule { + # id = computed + name = "rule-path-1" + rule_type = "PathBasedRouting" + url_path_map_name = "path-map-1" + + # http_listener_id = computed + http_listener_name = "listener-2" + } + + ssl_certificate { + # id = computed + name = "ssl-1" + data = "${file("testdata/application_gateway_test.pfx")}" + password = "terraform" + } + + tags { + environment = "tf01" + } +} +`, template) +} + func testAccAzureRMApplicationGateway_basic_changeSslCert(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_application_insights.go b/azurerm/resource_arm_application_insights.go index a54c580f5a21..25884cce196c 100644 --- a/azurerm/resource_arm_application_insights.go +++ b/azurerm/resource_arm_application_insights.go @@ -1,13 +1,16 @@ package azurerm import ( + "context" "fmt" "log" "net/http" + "time" "github.com/Azure/azure-sdk-for-go/services/appinsights/mgmt/2015-05-01/insights" "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 resourceArmApplicationInsights() *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": { @@ -72,6 +80,21 @@ func resourceArmApplicationInsightsCreateOrUpdate(d *schema.ResourceData, meta i name := d.Get("name").(string) resGroup := d.Get("resource_group_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 Application Insights %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_application_insights", *resp.ID) + } + } + applicationType := d.Get("application_type").(string) location := azureRMNormalizeLocation(d.Get("location").(string)) tags := d.Get("tags").(map[string]interface{}) @@ -89,7 +112,9 @@ func resourceArmApplicationInsightsCreateOrUpdate(d *schema.ResourceData, meta i Tags: expandTags(tags), } - resp, err := client.CreateOrUpdate(ctx, resGroup, name, insightProperties) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + resp, err := client.CreateOrUpdate(waitCtx, resGroup, name, insightProperties) if err != nil { // @tombuildsstuff - from 2018-08-14 the Create call started returning a 201 instead of 200 // which doesn't match the Swagger - this works around it until that's fixed @@ -121,7 +146,7 @@ func resourceArmApplicationInsightsRead(d *schema.ResourceData, meta interface{} return err } - log.Printf("[DEBUG] Reading AzureRM Application Insights '%s'", id) + log.Printf("[DEBUG] Reading AzureRM Application Insights %q", id) resGroup := id.ResourceGroup name := id.Path["components"] @@ -132,7 +157,7 @@ func resourceArmApplicationInsightsRead(d *schema.ResourceData, meta interface{} d.SetId("") return nil } - return fmt.Errorf("Error making Read request on AzureRM Application Insights '%s': %+v", name, err) + return fmt.Errorf("Error making Read request on AzureRM Application Insights %q: %+v", name, err) } d.Set("name", name) @@ -165,7 +190,9 @@ func resourceArmApplicationInsightsDelete(d *schema.ResourceData, meta interface log.Printf("[DEBUG] Deleting AzureRM Application Insights '%s' (resource group '%s')", name, resGroup) - 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 resp.StatusCode == http.StatusNotFound { return nil diff --git a/azurerm/resource_arm_application_insights_test.go b/azurerm/resource_arm_application_insights_test.go index 2243021ffb02..c2cf4c0243cf 100644 --- a/azurerm/resource_arm_application_insights_test.go +++ b/azurerm/resource_arm_application_insights_test.go @@ -31,6 +31,30 @@ func TestAccAzureRMApplicationInsights_basicWeb(t *testing.T) { }) } +func TestAccAzureRMApplicationInsights_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMApplicationInsightsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMApplicationInsights_basic(ri, location, "web"), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMApplicationInsightsExists("azurerm_application_insights.test"), + resource.TestCheckResourceAttr("azurerm_application_insights.test", "application_type", "web"), + ), + }, + { + Config: testAccAzureRMApplicationInsights_requiresImport(ri, location, "web"), + ExpectError: testRequiresImportError("azurerm_application_insights"), + }, + }, + }) +} + func TestAccAzureRMApplicationInsights_basicJava(t *testing.T) { ri := acctest.RandInt() @@ -228,3 +252,17 @@ resource "azurerm_application_insights" "test" { } `, rInt, location, rInt, applicationType) } + +func testAccAzureRMApplicationInsights_requiresImport(rInt int, location string, applicationType string) string { + template := testAccAzureRMApplicationInsights_basic(rInt, location, applicationType) + return fmt.Sprintf(` +%s + +resource "azurerm_application_insights" "import" { + name = "${azurerm_application_insights.test.name}" + location = "${azurerm_application_insights.test.location}" + resource_group_name = "${azurerm_application_insights.test.resource_group_name}" + application_type = "${azurerm_application_insights.test.application_type}" +} +`, template) +} diff --git a/azurerm/resource_arm_application_security_group.go b/azurerm/resource_arm_application_security_group.go index 51e681f0e40b..b3d159e44fa0 100644 --- a/azurerm/resource_arm_application_security_group.go +++ b/azurerm/resource_arm_application_security_group.go @@ -1,12 +1,15 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -19,6 +22,11 @@ func resourceArmApplicationSecurityGroup() *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": { @@ -42,6 +50,21 @@ func resourceArmApplicationSecurityGroupCreateUpdate(d *schema.ResourceData, met resourceGroup := 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, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Application Security Group %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_application_security_group", *resp.ID) + } + } + location := azureRMNormalizeLocation(d.Get("location").(string)) tags := d.Get("tags").(map[string]interface{}) @@ -54,7 +77,9 @@ func resourceArmApplicationSecurityGroupCreateUpdate(d *schema.ResourceData, met return fmt.Errorf("Error creating Application Security Group %q (Resource Group %q): %+v", name, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for the Application Security Group %q (Resource Group %q) to finish creating: %+v", name, resourceGroup, err) } @@ -123,7 +148,9 @@ func resourceArmApplicationSecurityGroupDelete(d *schema.ResourceData, meta inte } } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if !response.WasNotFound(future.Response()) { return fmt.Errorf("Error waiting for deletion of Application Security Group %q (Resource Group %q): %+v", name, resourceGroup, err) diff --git a/azurerm/resource_arm_application_security_group_test.go b/azurerm/resource_arm_application_security_group_test.go index a4387826f730..daa9f178e9d7 100644 --- a/azurerm/resource_arm_application_security_group_test.go +++ b/azurerm/resource_arm_application_security_group_test.go @@ -31,6 +31,30 @@ func TestAccAzureRMApplicationSecurityGroup_basic(t *testing.T) { }) } +func TestAccAzureRMApplicationSecurityGroup_requiresImport(t *testing.T) { + ri := acctest.RandInt() + resourceName := "azurerm_application_security_group.test" + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMApplicationSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMApplicationSecurityGroup_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMApplicationSecurityGroupExists(resourceName), + ), + }, + { + Config: testAccAzureRMApplicationSecurityGroup_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_application_security_group"), + }, + }, + }) +} + func TestAccAzureRMApplicationSecurityGroup_complete(t *testing.T) { ri := acctest.RandInt() resourceName := "azurerm_application_security_group.test" @@ -155,6 +179,19 @@ resource "azurerm_application_security_group" "test" { `, rInt, location, rInt) } +func testAccAzureRMApplicationSecurityGroup_requiresImport(rInt int, location string) string { + template := testAccAzureRMApplicationSecurityGroup_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_application_security_group" "import" { + name = "${azurerm_application_security_group.test.name}" + location = "${azurerm_application_security_group.test.location}" + resource_group_name = "${azurerm_application_security_group.test.resource_group_name}" +} +`, template) +} + func testAccAzureRMApplicationSecurityGroup_complete(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_automation_account.go b/azurerm/resource_arm_automation_account.go index 52ad4c512ff3..67bd8165ed98 100644 --- a/azurerm/resource_arm_automation_account.go +++ b/azurerm/resource_arm_automation_account.go @@ -1,13 +1,16 @@ package azurerm import ( + "context" "fmt" "log" "regexp" + "time" "github.com/Azure/azure-sdk-for-go/services/automation/mgmt/2015-10-31/automation" "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" ) @@ -17,10 +20,14 @@ func resourceArmAutomationAccount() *schema.Resource { Read: resourceArmAutomationAccountRead, Update: resourceArmAutomationAccountCreateUpdate, Delete: resourceArmAutomationAccountDelete, - 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": { @@ -69,10 +76,24 @@ func resourceArmAutomationAccountCreateUpdate(d *schema.ResourceData, meta inter log.Printf("[INFO] preparing arguments for AzureRM Automation Account creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_name").(string) - tags := d.Get("tags").(map[string]interface{}) + 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 Automation Account %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_automation_account", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) + tags := d.Get("tags").(map[string]interface{}) sku := expandAutomationAccountSku(d) parameters := automation.AccountCreateOrUpdateParameters{ @@ -84,7 +105,9 @@ func resourceArmAutomationAccountCreateUpdate(d *schema.ResourceData, meta inter Tags: expandTags(tags), } - _, err := client.CreateOrUpdate(ctx, resGroup, name, parameters) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, resGroup, name, parameters) if err != nil { return err } @@ -150,7 +173,9 @@ func resourceArmAutomationAccountDelete(d *schema.ResourceData, meta interface{} resGroup := id.ResourceGroup name := id.Path["automationAccounts"] - 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) { diff --git a/azurerm/resource_arm_automation_account_test.go b/azurerm/resource_arm_automation_account_test.go index 9cfbe654108a..382e0f7e2eac 100644 --- a/azurerm/resource_arm_automation_account_test.go +++ b/azurerm/resource_arm_automation_account_test.go @@ -35,6 +35,31 @@ func TestAccAzureRMAutomationAccount_basic(t *testing.T) { }) } +func TestAccAzureRMAutomationAccount_requireImport(t *testing.T) { + resourceName := "azurerm_automation_account.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAutomationAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAutomationAccount_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAutomationAccountExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "sku.0.name", "Basic"), + ), + }, + { + Config: testAccAzureRMAutomationAccount_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_automation_account"), + }, + }, + }) +} + func TestAccAzureRMAutomationAccount_complete(t *testing.T) { resourceName := "azurerm_automation_account.test" ri := acctest.RandInt() @@ -132,6 +157,7 @@ resource "azurerm_automation_account" "test" { name = "acctest-%d" location = "${azurerm_resource_group.test.location}" resource_group_name = "${azurerm_resource_group.test.name}" + sku { name = "Basic" } @@ -139,6 +165,23 @@ resource "azurerm_automation_account" "test" { `, rInt, location, rInt) } +func testAccAzureRMAutomationAccount_requiresImport(rInt int, location string) string { + template := testAccAzureRMAutomationAccount_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_automation_account" "import" { + name = "${azurerm_automation_account.test.name}" + location = "${azurerm_automation_account.test.location}" + resource_group_name = "${azurerm_automation_account.test.resource_group_name}" + + sku { + name = "Basic" + } +} +`, template) +} + func testAccAzureRMAutomationAccount_complete(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_automation_credential.go b/azurerm/resource_arm_automation_credential.go index 3767bef02fdd..e7c3b9a4fca7 100644 --- a/azurerm/resource_arm_automation_credential.go +++ b/azurerm/resource_arm_automation_credential.go @@ -1,11 +1,14 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/automation/mgmt/2015-10-31/automation" "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" ) @@ -15,10 +18,14 @@ func resourceArmAutomationCredential() *schema.Resource { Read: resourceArmAutomationCredentialRead, Update: resourceArmAutomationCredentialCreateUpdate, Delete: resourceArmAutomationCredentialDelete, - 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": { @@ -63,6 +70,21 @@ func resourceArmAutomationCredentialCreateUpdate(d *schema.ResourceData, meta in name := d.Get("name").(string) resGroup := d.Get("resource_group_name").(string) accName := d.Get("account_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, accName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Automation Credential %q (Account %q / Resource Group %q): %+v", name, accName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_automation_credential", *resp.ID) + } + } + user := d.Get("username").(string) password := d.Get("password").(string) description := d.Get("description").(string) @@ -76,7 +98,9 @@ func resourceArmAutomationCredentialCreateUpdate(d *schema.ResourceData, meta in Name: &name, } - _, err := client.CreateOrUpdate(ctx, resGroup, accName, name, parameters) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, resGroup, accName, name, parameters) if err != nil { return err } @@ -140,7 +164,9 @@ func resourceArmAutomationCredentialDelete(d *schema.ResourceData, meta interfac accName := id.Path["automationAccounts"] name := id.Path["credentials"] - resp, err := client.Delete(ctx, resGroup, accName, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resGroup, accName, name) if err != nil { if utils.ResponseWasNotFound(resp) { return nil diff --git a/azurerm/resource_arm_automation_credential_test.go b/azurerm/resource_arm_automation_credential_test.go index 6ffb7ac94665..c4397139425d 100644 --- a/azurerm/resource_arm_automation_credential_test.go +++ b/azurerm/resource_arm_automation_credential_test.go @@ -36,6 +36,31 @@ func TestAccAzureRMAutomationCredential_basic(t *testing.T) { }) } +func TestAccAzureRMAutomationCredential_requiresImport(t *testing.T) { + resourceName := "azurerm_automation_credential.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAutomationCredentialDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAutomationCredential_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAutomationCredentialExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "username", "test_user"), + ), + }, + { + Config: testAccAzureRMAutomationCredential_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_automation_credential"), + }, + }, + }) +} + func TestAccAzureRMAutomationCredential_complete(t *testing.T) { resourceName := "azurerm_automation_credential.test" ri := acctest.RandInt() @@ -157,6 +182,21 @@ resource "azurerm_automation_credential" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMAutomationCredential_requiresImport(rInt int, location string) string { + template := testAccAzureRMAutomationCredential_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_automation_credential" "import" { + name = "${azurerm_automation_credential.test.name}" + resource_group_name = "${azurerm_automation_credential.test.resource_group_name}" + account_name = "${azurerm_automation_credential.test.account_name}" + username = "${azurerm_automation_credential.test.username}" + password = "${azurerm_automation_credential.test.password}" +} +`, template) +} + func testAccAzureRMAutomationCredential_complete(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_automation_runbook.go b/azurerm/resource_arm_automation_runbook.go index a6ccbf8ac6ad..9c242f17fc41 100644 --- a/azurerm/resource_arm_automation_runbook.go +++ b/azurerm/resource_arm_automation_runbook.go @@ -2,13 +2,16 @@ package azurerm import ( "bytes" + "context" "fmt" "io/ioutil" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/automation/mgmt/2015-10-31/automation" "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" ) @@ -18,10 +21,14 @@ func resourceArmAutomationRunbook() *schema.Resource { Read: resourceArmAutomationRunbookRead, Update: resourceArmAutomationRunbookCreateUpdate, Delete: resourceArmAutomationRunbookDelete, - 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": { @@ -125,15 +132,29 @@ func resourceArmAutomationRunbookCreateUpdate(d *schema.ResourceData, meta inter log.Printf("[INFO] preparing arguments for AzureRM Automation Runbook creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) + accName := d.Get("account_name").(string) resGroup := d.Get("resource_group_name").(string) - tags := d.Get("tags").(map[string]interface{}) - accName := d.Get("account_name").(string) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, accName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Automation Runbook %q (Account %q / Resource Group %q): %+v", name, accName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_automation_runbook", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) runbookType := automation.RunbookTypeEnum(d.Get("runbook_type").(string)) logProgress := d.Get("log_progress").(bool) logVerbose := d.Get("log_verbose").(bool) description := d.Get("description").(string) + tags := d.Get("tags").(map[string]interface{}) contentLink := expandContentLink(d) @@ -150,7 +171,9 @@ func resourceArmAutomationRunbookCreateUpdate(d *schema.ResourceData, meta inter Tags: expandTags(tags), } - _, err := client.CreateOrUpdate(ctx, resGroup, accName, name, parameters) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, resGroup, accName, name, parameters) if err != nil { return fmt.Errorf("Error creating/updating Automation Runbook %q (Account %q / Resource Group %q): %+v", name, accName, resGroup, err) } @@ -253,7 +276,9 @@ func resourceArmAutomationRunbookDelete(d *schema.ResourceData, meta interface{} accName := id.Path["automationAccounts"] name := id.Path["runbooks"] - resp, err := client.Delete(ctx, resGroup, accName, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resGroup, accName, name) if err != nil { if utils.ResponseWasNotFound(resp) { return nil diff --git a/azurerm/resource_arm_automation_runbook_test.go b/azurerm/resource_arm_automation_runbook_test.go index 14fd140789a0..65a4f7d50e16 100644 --- a/azurerm/resource_arm_automation_runbook_test.go +++ b/azurerm/resource_arm_automation_runbook_test.go @@ -36,6 +36,31 @@ func TestAccAzureRMAutomationRunbook_PSWorkflow(t *testing.T) { }) } +func TestAccAzureRMAutomationRunbook_requiresImport(t *testing.T) { + resourceName := "azurerm_automation_runbook.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAutomationRunbookDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAutomationRunbook_PSWorkflow(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAutomationRunbookExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "runbook_type", "PowerShellWorkflow"), + ), + }, + { + Config: testAccAzureRMAutomationRunbook_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_automation_runbook"), + }, + }, + }) +} + func TestAccAzureRMAutomationRunbook_PSWorkflowWithHash(t *testing.T) { resourceName := "azurerm_automation_runbook.test" ri := acctest.RandInt() @@ -173,7 +198,7 @@ resource "azurerm_automation_account" "test" { } resource "azurerm_automation_runbook" "test" { - name = "Get-AzureVMTutorial" + name = "Get-AzureVMTutorial" location = "${azurerm_resource_group.test.location}" resource_group_name = "${azurerm_resource_group.test.name}" @@ -189,6 +214,27 @@ resource "azurerm_automation_runbook" "test" { `, rInt, location, rInt) } +func testAccAzureRMAutomationRunbook_requiresImport(rInt int, location string) string { + template := testAccAzureRMAutomationRunbook_PSWorkflow(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_automation_runbook" "import" { + name = "${azurerm_automation_runbook.test.name}" + location = "${azurerm_automation_runbook.test.location}" + resource_group_name = "${azurerm_automation_runbook.test.resource_group_name}" + account_name = "${azurerm_automation_runbook.test.account_name}" + log_verbose = "true" + log_progress = "true" + description = "This is a test runbook for terraform acceptance test" + runbook_type = "PowerShellWorkflow" + publish_content_link { + uri = "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/101-automation-runbook-getvms/Runbooks/Get-AzureVMTutorial.ps1" + } +} +`, template) +} + func testAccAzureRMAutomationRunbook_PSWorkflowWithHash(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_automation_schedule.go b/azurerm/resource_arm_automation_schedule.go index d8b1edcc559c..a2f15bc44653 100644 --- a/azurerm/resource_arm_automation_schedule.go +++ b/azurerm/resource_arm_automation_schedule.go @@ -1,6 +1,7 @@ package azurerm import ( + "context" "fmt" "log" "regexp" @@ -13,6 +14,7 @@ import ( "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/set" "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/utils" ) @@ -23,10 +25,14 @@ func resourceArmAutomationSchedule() *schema.Resource { Read: resourceArmAutomationScheduleRead, Update: resourceArmAutomationScheduleCreateUpdate, Delete: resourceArmAutomationScheduleDelete, - 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": { @@ -222,10 +228,6 @@ func resourceArmAutomationScheduleCreateUpdate(d *schema.ResourceData, meta inte name := d.Get("name").(string) resGroup := d.Get("resource_group_name").(string) - frequency := d.Get("frequency").(string) - - timeZone := d.Get("timezone").(string) - description := d.Get("description").(string) //CustomizeDiff should ensure one of these two is set //todo remove this once `account_name` is removed @@ -236,6 +238,24 @@ func resourceArmAutomationScheduleCreateUpdate(d *schema.ResourceData, meta inte accountName = v.(string) } + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, accountName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Automation Schedule %q (Account %q / Resource Group %q): %+v", name, accountName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_automation_schedule", *resp.ID) + } + } + + frequency := d.Get("frequency").(string) + timeZone := d.Get("timezone").(string) + description := d.Get("description").(string) + parameters := automation.ScheduleCreateOrUpdateParameters{ Name: &name, ScheduleCreateOrUpdateProperties: &automation.ScheduleCreateOrUpdateProperties{ @@ -277,7 +297,9 @@ func resourceArmAutomationScheduleCreateUpdate(d *schema.ResourceData, meta inte properties.AdvancedSchedule = advancedRef } - _, err := client.CreateOrUpdate(ctx, resGroup, accountName, name, parameters) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, resGroup, accountName, name, parameters) if err != nil { return err } @@ -369,7 +391,9 @@ func resourceArmAutomationScheduleDelete(d *schema.ResourceData, meta interface{ resGroup := id.ResourceGroup accountName := id.Path["automationAccounts"] - resp, err := client.Delete(ctx, resGroup, accountName, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resGroup, accountName, name) if err != nil { if !utils.ResponseWasNotFound(resp) { return fmt.Errorf("Error issuing AzureRM delete request for Automation Schedule '%s': %+v", name, err) diff --git a/azurerm/resource_arm_automation_schedule_test.go b/azurerm/resource_arm_automation_schedule_test.go index 06a6173c343a..504ff61c219a 100644 --- a/azurerm/resource_arm_automation_schedule_test.go +++ b/azurerm/resource_arm_automation_schedule_test.go @@ -2,7 +2,6 @@ package azurerm import ( "fmt" - "strconv" "testing" "time" @@ -12,7 +11,7 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) -func TestAccAzureRMAutomationSchedule_oneTime_basic(t *testing.T) { +func TestAccAzureRMAutomationSchedule_oneTimeBasic(t *testing.T) { resourceName := "azurerm_automation_schedule.test" ri := acctest.RandInt() @@ -22,8 +21,16 @@ func TestAccAzureRMAutomationSchedule_oneTime_basic(t *testing.T) { CheckDestroy: testCheckAzureRMAutomationScheduleDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMAutomationSchedule_oneTime_basic(ri, testLocation()), - Check: checkAccAzureRMAutomationSchedule_oneTime_basic(resourceName), + Config: testAccAzureRMAutomationSchedule_oneTimeBasic(ri, testLocation()), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMAutomationScheduleExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "name"), + resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), + resource.TestCheckResourceAttrSet(resourceName, "automation_account_name"), + resource.TestCheckResourceAttrSet(resourceName, "start_time"), + resource.TestCheckResourceAttr(resourceName, "frequency", "OneTime"), + resource.TestCheckResourceAttr(resourceName, "timezone", "UTC"), + ), }, { ResourceName: resourceName, @@ -34,7 +41,31 @@ func TestAccAzureRMAutomationSchedule_oneTime_basic(t *testing.T) { }) } -func TestAccAzureRMAutomationSchedule_oneTime_complete(t *testing.T) { +func TestAccAzureRMAutomationSchedule_requiresImport(t *testing.T) { + resourceName := "azurerm_automation_schedule.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAutomationScheduleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAutomationSchedule_oneTimeBasic(ri, location), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMAutomationScheduleExists(resourceName), + ), + }, + { + Config: testAccAzureRMAutomationSchedule_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_automation_schedule"), + }, + }, + }) +} + +func TestAccAzureRMAutomationSchedule_oneTimeComplete(t *testing.T) { resourceName := "azurerm_automation_schedule.test" ri := acctest.RandInt() @@ -49,8 +80,17 @@ func TestAccAzureRMAutomationSchedule_oneTime_complete(t *testing.T) { CheckDestroy: testCheckAzureRMAutomationScheduleDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMAutomationSchedule_oneTime_complete(ri, testLocation(), startTime), - Check: checkAccAzureRMAutomationSchedule_oneTime_complete(resourceName, startTime), + Config: testAccAzureRMAutomationSchedule_oneTimeComplete(ri, testLocation(), startTime), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMAutomationScheduleExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "name"), + resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), + resource.TestCheckResourceAttrSet(resourceName, "automation_account_name"), + resource.TestCheckResourceAttr(resourceName, "frequency", "OneTime"), + resource.TestCheckResourceAttr(resourceName, "start_time", startTime), + resource.TestCheckResourceAttr(resourceName, "timezone", "Central Europe Standard Time"), + resource.TestCheckResourceAttr(resourceName, "description", "This is an automation schedule"), + ), }, { ResourceName: resourceName, @@ -61,7 +101,7 @@ func TestAccAzureRMAutomationSchedule_oneTime_complete(t *testing.T) { }) } -func TestAccAzureRMAutomationSchedule_oneTime_update(t *testing.T) { +func TestAccAzureRMAutomationSchedule_oneTimeUpdate(t *testing.T) { resourceName := "azurerm_automation_schedule.test" ri := acctest.RandInt() @@ -76,18 +116,35 @@ func TestAccAzureRMAutomationSchedule_oneTime_update(t *testing.T) { CheckDestroy: testCheckAzureRMAutomationScheduleDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMAutomationSchedule_oneTime_basic(ri, testLocation()), - Check: checkAccAzureRMAutomationSchedule_oneTime_basic(resourceName), + Config: testAccAzureRMAutomationSchedule_oneTimeBasic(ri, testLocation()), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMAutomationScheduleExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "name"), + resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), + resource.TestCheckResourceAttrSet(resourceName, "automation_account_name"), + resource.TestCheckResourceAttrSet(resourceName, "start_time"), + resource.TestCheckResourceAttr(resourceName, "frequency", "OneTime"), + resource.TestCheckResourceAttr(resourceName, "timezone", "UTC"), + ), }, { - Config: testAccAzureRMAutomationSchedule_oneTime_complete(ri, testLocation(), startTime), - Check: checkAccAzureRMAutomationSchedule_oneTime_complete(resourceName, startTime), + Config: testAccAzureRMAutomationSchedule_oneTimeComplete(ri, testLocation(), startTime), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMAutomationScheduleExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "name"), + resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), + resource.TestCheckResourceAttrSet(resourceName, "automation_account_name"), + resource.TestCheckResourceAttr(resourceName, "frequency", "OneTime"), + resource.TestCheckResourceAttr(resourceName, "start_time", startTime), + resource.TestCheckResourceAttr(resourceName, "timezone", "Central Europe Standard Time"), + resource.TestCheckResourceAttr(resourceName, "description", "This is an automation schedule"), + ), }, }, }) } -func TestAccAzureRMAutomationSchedule_hourly(t *testing.T) { +func TestAccAzureRMAutomationSchedule_recurringHourly(t *testing.T) { resourceName := "azurerm_automation_schedule.test" ri := acctest.RandInt() @@ -97,8 +154,17 @@ func TestAccAzureRMAutomationSchedule_hourly(t *testing.T) { CheckDestroy: testCheckAzureRMAutomationScheduleDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMAutomationSchedule_recurring_basic(ri, testLocation(), "Hour", 7), - Check: checkAccAzureRMAutomationSchedule_recurring_basic(resourceName, "Hour", 7), + Config: testAccAzureRMAutomationSchedule_recurringBasic(ri, testLocation(), "Hour", 7), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMAutomationScheduleExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "name"), + resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), + resource.TestCheckResourceAttrSet(resourceName, "automation_account_name"), + resource.TestCheckResourceAttrSet(resourceName, "start_time"), + resource.TestCheckResourceAttr(resourceName, "frequency", "Hour"), + resource.TestCheckResourceAttr(resourceName, "interval", "7"), + resource.TestCheckResourceAttr(resourceName, "timezone", "UTC"), + ), }, { ResourceName: resourceName, @@ -109,7 +175,7 @@ func TestAccAzureRMAutomationSchedule_hourly(t *testing.T) { }) } -func TestAccAzureRMAutomationSchedule_daily(t *testing.T) { +func TestAccAzureRMAutomationSchedule_recurringDaily(t *testing.T) { resourceName := "azurerm_automation_schedule.test" ri := acctest.RandInt() @@ -119,8 +185,17 @@ func TestAccAzureRMAutomationSchedule_daily(t *testing.T) { CheckDestroy: testCheckAzureRMAutomationScheduleDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMAutomationSchedule_recurring_basic(ri, testLocation(), "Day", 7), - Check: checkAccAzureRMAutomationSchedule_recurring_basic(resourceName, "Day", 7), + Config: testAccAzureRMAutomationSchedule_recurringBasic(ri, testLocation(), "Day", 7), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMAutomationScheduleExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "name"), + resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), + resource.TestCheckResourceAttrSet(resourceName, "automation_account_name"), + resource.TestCheckResourceAttrSet(resourceName, "start_time"), + resource.TestCheckResourceAttr(resourceName, "frequency", "Day"), + resource.TestCheckResourceAttr(resourceName, "interval", "7"), + resource.TestCheckResourceAttr(resourceName, "timezone", "UTC"), + ), }, { ResourceName: resourceName, @@ -131,7 +206,7 @@ func TestAccAzureRMAutomationSchedule_daily(t *testing.T) { }) } -func TestAccAzureRMAutomationSchedule_weekly(t *testing.T) { +func TestAccAzureRMAutomationSchedule_recurringWeekly(t *testing.T) { resourceName := "azurerm_automation_schedule.test" ri := acctest.RandInt() @@ -141,8 +216,17 @@ func TestAccAzureRMAutomationSchedule_weekly(t *testing.T) { CheckDestroy: testCheckAzureRMAutomationScheduleDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMAutomationSchedule_recurring_basic(ri, testLocation(), "Week", 7), - Check: checkAccAzureRMAutomationSchedule_recurring_basic(resourceName, "Week", 7), + Config: testAccAzureRMAutomationSchedule_recurringBasic(ri, testLocation(), "Week", 7), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMAutomationScheduleExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "name"), + resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), + resource.TestCheckResourceAttrSet(resourceName, "automation_account_name"), + resource.TestCheckResourceAttrSet(resourceName, "start_time"), + resource.TestCheckResourceAttr(resourceName, "frequency", "Week"), + resource.TestCheckResourceAttr(resourceName, "interval", "7"), + resource.TestCheckResourceAttr(resourceName, "timezone", "UTC"), + ), }, { ResourceName: resourceName, @@ -153,7 +237,7 @@ func TestAccAzureRMAutomationSchedule_weekly(t *testing.T) { }) } -func TestAccAzureRMAutomationSchedule_monthly(t *testing.T) { +func TestAccAzureRMAutomationSchedule_recurringMonthly(t *testing.T) { resourceName := "azurerm_automation_schedule.test" ri := acctest.RandInt() @@ -163,8 +247,17 @@ func TestAccAzureRMAutomationSchedule_monthly(t *testing.T) { CheckDestroy: testCheckAzureRMAutomationScheduleDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMAutomationSchedule_recurring_basic(ri, testLocation(), "Month", 7), - Check: checkAccAzureRMAutomationSchedule_recurring_basic(resourceName, "Month", 7), + Config: testAccAzureRMAutomationSchedule_recurringBasic(ri, testLocation(), "Month", 7), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMAutomationScheduleExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "name"), + resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), + resource.TestCheckResourceAttrSet(resourceName, "automation_account_name"), + resource.TestCheckResourceAttrSet(resourceName, "start_time"), + resource.TestCheckResourceAttr(resourceName, "frequency", "Month"), + resource.TestCheckResourceAttr(resourceName, "interval", "7"), + resource.TestCheckResourceAttr(resourceName, "timezone", "UTC"), + ), }, { ResourceName: resourceName, @@ -175,7 +268,7 @@ func TestAccAzureRMAutomationSchedule_monthly(t *testing.T) { }) } -func TestAccAzureRMAutomationSchedule_weekly_advanced(t *testing.T) { +func TestAccAzureRMAutomationSchedule_weeklyAdvanced(t *testing.T) { resourceName := "azurerm_automation_schedule.test" ri := acctest.RandInt() @@ -185,8 +278,18 @@ func TestAccAzureRMAutomationSchedule_weekly_advanced(t *testing.T) { CheckDestroy: testCheckAzureRMAutomationScheduleDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMAutomationSchedule_recurring_advanced_week(ri, testLocation(), "Monday"), - Check: checkAccAzureRMAutomationSchedule_recurring_advanced_week(resourceName, "Monday"), + Config: testAccAzureRMAutomationSchedule_recurringAdvancedWeek(ri, testLocation(), "Monday"), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMAutomationScheduleExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "name"), + resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), + resource.TestCheckResourceAttrSet(resourceName, "automation_account_name"), + resource.TestCheckResourceAttrSet(resourceName, "start_time"), + resource.TestCheckResourceAttr(resourceName, "frequency", "Week"), + resource.TestCheckResourceAttr(resourceName, "interval", "1"), + resource.TestCheckResourceAttr(resourceName, "timezone", "UTC"), + resource.TestCheckResourceAttr(resourceName, "week_days.#", "1"), + ), }, { ResourceName: resourceName, @@ -197,7 +300,7 @@ func TestAccAzureRMAutomationSchedule_weekly_advanced(t *testing.T) { }) } -func TestAccAzureRMAutomationSchedule_monthly_advanced_by_day(t *testing.T) { +func TestAccAzureRMAutomationSchedule_monthlyAdvancedByDay(t *testing.T) { resourceName := "azurerm_automation_schedule.test" ri := acctest.RandInt() @@ -207,8 +310,18 @@ func TestAccAzureRMAutomationSchedule_monthly_advanced_by_day(t *testing.T) { CheckDestroy: testCheckAzureRMAutomationScheduleDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMAutomationSchedule_recurring_advanced_month(ri, testLocation(), 2), - Check: checkAccAzureRMAutomationSchedule_recurring_advanced_month(resourceName, 2), + Config: testAccAzureRMAutomationSchedule_recurringAdvancedMonth(ri, testLocation(), 2), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMAutomationScheduleExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "name"), + resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), + resource.TestCheckResourceAttrSet(resourceName, "automation_account_name"), + resource.TestCheckResourceAttrSet(resourceName, "start_time"), + resource.TestCheckResourceAttr(resourceName, "frequency", "Month"), + resource.TestCheckResourceAttr(resourceName, "interval", "1"), + resource.TestCheckResourceAttr(resourceName, "timezone", "UTC"), + resource.TestCheckResourceAttr(resourceName, "month_days.#", "1"), + ), }, { ResourceName: resourceName, @@ -219,7 +332,7 @@ func TestAccAzureRMAutomationSchedule_monthly_advanced_by_day(t *testing.T) { }) } -func TestAccAzureRMAutomationSchedule_monthly_advanced_by_week_day(t *testing.T) { +func TestAccAzureRMAutomationSchedule_monthlyAdvancedByWeekday(t *testing.T) { resourceName := "azurerm_automation_schedule.test" ri := acctest.RandInt() @@ -229,8 +342,20 @@ func TestAccAzureRMAutomationSchedule_monthly_advanced_by_week_day(t *testing.T) CheckDestroy: testCheckAzureRMAutomationScheduleDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMAutomationSchedule_recurring_advanced_month_week_day(ri, testLocation(), "Monday", 2), - Check: checkAccAzureRMAutomationSchedule_recurring_advanced_month_week_day(resourceName, "Monday", 2), + Config: testAccAzureRMAutomationSchedule_recurringAdvancedMonthWeekDay(ri, testLocation(), "Monday", 2), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMAutomationScheduleExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "name"), + resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), + resource.TestCheckResourceAttrSet(resourceName, "automation_account_name"), + resource.TestCheckResourceAttrSet(resourceName, "start_time"), + resource.TestCheckResourceAttr(resourceName, "frequency", "Month"), + resource.TestCheckResourceAttr(resourceName, "interval", "1"), + resource.TestCheckResourceAttr(resourceName, "timezone", "UTC"), + resource.TestCheckResourceAttr(resourceName, "monthly_occurrence.#", "1"), + resource.TestCheckResourceAttr(resourceName, "monthly_occurrence.0.day", "Monday"), + resource.TestCheckResourceAttr(resourceName, "monthly_occurrence.0.occurrence", "2"), + ), }, { ResourceName: resourceName, @@ -306,26 +431,8 @@ func testCheckAzureRMAutomationScheduleExists(name string) resource.TestCheckFun } } -func testAccAzureRMAutomationSchedule_prerequisites(rInt int, location string) string { - return fmt.Sprintf(` -resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" -} - -resource "azurerm_automation_account" "test" { - name = "acctestAA-%d" - location = "${azurerm_resource_group.test.location}" - resource_group_name = "${azurerm_resource_group.test.name}" - sku { - name = "Basic" - } -} - -`, rInt, location, rInt) -} - -func testAccAzureRMAutomationSchedule_oneTime_basic(rInt int, location string) string { +func testAccAzureRMAutomationSchedule_oneTimeBasic(rInt int, location string) string { + template := testAccAzureRMAutomationSchedule_template(rInt, location) return fmt.Sprintf(` %s @@ -335,22 +442,25 @@ resource "azurerm_automation_schedule" "test" { automation_account_name = "${azurerm_automation_account.test.name}" frequency = "OneTime" } -`, testAccAzureRMAutomationSchedule_prerequisites(rInt, location), rInt) +`, template, rInt) } -func checkAccAzureRMAutomationSchedule_oneTime_basic(resourceName string) resource.TestCheckFunc { - return resource.ComposeAggregateTestCheckFunc( - testCheckAzureRMAutomationScheduleExists("azurerm_automation_schedule.test"), - resource.TestCheckResourceAttrSet(resourceName, "name"), - resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), - resource.TestCheckResourceAttrSet(resourceName, "automation_account_name"), - resource.TestCheckResourceAttrSet(resourceName, "start_time"), - resource.TestCheckResourceAttr(resourceName, "frequency", "OneTime"), - resource.TestCheckResourceAttr(resourceName, "timezone", "UTC"), - ) +func testAccAzureRMAutomationSchedule_requiresImport(rInt int, location string) string { + template := testAccAzureRMAutomationSchedule_oneTimeBasic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_automation_schedule" "import" { + name = "${azurerm_automation_schedule.test.name}" + resource_group_name = "${azurerm_automation_schedule.test.resource_group_name}" + automation_account_name = "${azurerm_automation_schedule.test.automation_account_name}" + frequency = "OneTime" +} +`, template) } -func testAccAzureRMAutomationSchedule_oneTime_complete(rInt int, location, startTime string) string { +func testAccAzureRMAutomationSchedule_oneTimeComplete(rInt int, location, startTime string) string { + template := testAccAzureRMAutomationSchedule_template(rInt, location) return fmt.Sprintf(` %s @@ -363,23 +473,10 @@ resource "azurerm_automation_schedule" "test" { timezone = "Central Europe Standard Time" description = "This is an automation schedule" } -`, testAccAzureRMAutomationSchedule_prerequisites(rInt, location), rInt, startTime) -} - -func checkAccAzureRMAutomationSchedule_oneTime_complete(resourceName, startTime string) resource.TestCheckFunc { - return resource.ComposeAggregateTestCheckFunc( - testCheckAzureRMAutomationScheduleExists(resourceName), - resource.TestCheckResourceAttrSet(resourceName, "name"), - resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), - resource.TestCheckResourceAttrSet(resourceName, "automation_account_name"), - resource.TestCheckResourceAttr(resourceName, "frequency", "OneTime"), - resource.TestCheckResourceAttr(resourceName, "start_time", startTime), - resource.TestCheckResourceAttr(resourceName, "timezone", "Central Europe Standard Time"), - resource.TestCheckResourceAttr(resourceName, "description", "This is an automation schedule"), - ) +`, template, rInt, startTime) } -func testAccAzureRMAutomationSchedule_recurring_basic(rInt int, location, frequency string, interval int) string { +func testAccAzureRMAutomationSchedule_recurringBasic(rInt int, location, frequency string, interval int) string { return fmt.Sprintf(` %s @@ -390,23 +487,10 @@ resource "azurerm_automation_schedule" "test" { frequency = "%s" interval = "%d" } -`, testAccAzureRMAutomationSchedule_prerequisites(rInt, location), rInt, frequency, interval) -} - -func checkAccAzureRMAutomationSchedule_recurring_basic(resourceName string, frequency string, interval int) resource.TestCheckFunc { - return resource.ComposeAggregateTestCheckFunc( - testCheckAzureRMAutomationScheduleExists("azurerm_automation_schedule.test"), - resource.TestCheckResourceAttrSet(resourceName, "name"), - resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), - resource.TestCheckResourceAttrSet(resourceName, "automation_account_name"), - resource.TestCheckResourceAttrSet(resourceName, "start_time"), - resource.TestCheckResourceAttr(resourceName, "frequency", frequency), - resource.TestCheckResourceAttr(resourceName, "interval", strconv.Itoa(interval)), - resource.TestCheckResourceAttr(resourceName, "timezone", "UTC"), - ) +`, testAccAzureRMAutomationSchedule_template(rInt, location), rInt, frequency, interval) } -func testAccAzureRMAutomationSchedule_recurring_advanced_week(rInt int, location string, weekDay string) string { +func testAccAzureRMAutomationSchedule_recurringAdvancedWeek(rInt int, location string, weekDay string) string { return fmt.Sprintf(` %s @@ -418,24 +502,11 @@ resource "azurerm_automation_schedule" "test" { interval = "1" week_days = ["%s"] } -`, testAccAzureRMAutomationSchedule_prerequisites(rInt, location), rInt, weekDay) -} - -func checkAccAzureRMAutomationSchedule_recurring_advanced_week(resourceName string, weekDay string) resource.TestCheckFunc { - return resource.ComposeAggregateTestCheckFunc( - testCheckAzureRMAutomationScheduleExists("azurerm_automation_schedule.test"), - resource.TestCheckResourceAttrSet(resourceName, "name"), - resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), - resource.TestCheckResourceAttrSet(resourceName, "automation_account_name"), - resource.TestCheckResourceAttrSet(resourceName, "start_time"), - resource.TestCheckResourceAttr(resourceName, "frequency", "Week"), - resource.TestCheckResourceAttr(resourceName, "interval", "1"), - resource.TestCheckResourceAttr(resourceName, "timezone", "UTC"), - resource.TestCheckResourceAttr(resourceName, "week_days.#", "1"), - ) +`, testAccAzureRMAutomationSchedule_template(rInt, location), rInt, weekDay) } -func testAccAzureRMAutomationSchedule_recurring_advanced_month(rInt int, location string, monthDay int) string { +func testAccAzureRMAutomationSchedule_recurringAdvancedMonth(rInt int, location string, monthDay int) string { + template := testAccAzureRMAutomationSchedule_template(rInt, location) return fmt.Sprintf(` %s @@ -447,25 +518,11 @@ resource "azurerm_automation_schedule" "test" { interval = "1" month_days = [%d] } -`, testAccAzureRMAutomationSchedule_prerequisites(rInt, location), rInt, monthDay) -} - -func checkAccAzureRMAutomationSchedule_recurring_advanced_month(resourceName string, monthDay int) resource.TestCheckFunc { - return resource.ComposeAggregateTestCheckFunc( - testCheckAzureRMAutomationScheduleExists("azurerm_automation_schedule.test"), - resource.TestCheckResourceAttrSet(resourceName, "name"), - resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), - resource.TestCheckResourceAttrSet(resourceName, "automation_account_name"), - resource.TestCheckResourceAttrSet(resourceName, "start_time"), - resource.TestCheckResourceAttr(resourceName, "frequency", "Month"), - resource.TestCheckResourceAttr(resourceName, "interval", "1"), - resource.TestCheckResourceAttr(resourceName, "timezone", "UTC"), - resource.TestCheckResourceAttr(resourceName, "month_days.#", "1"), - ) +`, template, rInt, monthDay) } -func testAccAzureRMAutomationSchedule_recurring_advanced_month_week_day(rInt int, location string, weekDay string, weekDayOccurrence int) string { - +func testAccAzureRMAutomationSchedule_recurringAdvancedMonthWeekDay(rInt int, location string, weekDay string, weekDayOccurrence int) string { + template := testAccAzureRMAutomationSchedule_template(rInt, location) return fmt.Sprintf(` %s @@ -481,21 +538,23 @@ resource "azurerm_automation_schedule" "test" { occurrence = "%d" } } -`, testAccAzureRMAutomationSchedule_prerequisites(rInt, location), rInt, weekDay, weekDayOccurrence) +`, template, rInt, weekDay, weekDayOccurrence) } -func checkAccAzureRMAutomationSchedule_recurring_advanced_month_week_day(resourceName string, monthWeekDay string, monthWeekOccurrence int) resource.TestCheckFunc { - return resource.ComposeAggregateTestCheckFunc( - testCheckAzureRMAutomationScheduleExists("azurerm_automation_schedule.test"), - resource.TestCheckResourceAttrSet(resourceName, "name"), - resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), - resource.TestCheckResourceAttrSet(resourceName, "automation_account_name"), - resource.TestCheckResourceAttrSet(resourceName, "start_time"), - resource.TestCheckResourceAttr(resourceName, "frequency", "Month"), - resource.TestCheckResourceAttr(resourceName, "interval", "1"), - resource.TestCheckResourceAttr(resourceName, "timezone", "UTC"), - resource.TestCheckResourceAttr(resourceName, "monthly_occurrence.#", "1"), - resource.TestCheckResourceAttr(resourceName, "monthly_occurrence.0.day", monthWeekDay), - resource.TestCheckResourceAttr(resourceName, "monthly_occurrence.0.occurrence", strconv.Itoa(monthWeekOccurrence)), - ) +func testAccAzureRMAutomationSchedule_template(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_automation_account" "test" { + name = "acctestAA-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku { + name = "Basic" + } +} +`, rInt, location, rInt) } diff --git a/azurerm/resource_arm_autoscale_setting.go b/azurerm/resource_arm_autoscale_setting.go index 36efaa0f20f7..7b6c74d9e95d 100644 --- a/azurerm/resource_arm_autoscale_setting.go +++ b/azurerm/resource_arm_autoscale_setting.go @@ -1,6 +1,7 @@ package azurerm import ( + "context" "fmt" "log" "strconv" @@ -12,9 +13,11 @@ import ( "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) +// TODO: consider renaming this `azurerm_monitor_autoscale_setting` for consistency func resourceArmAutoScaleSetting() *schema.Resource { return &schema.Resource{ Create: resourceArmAutoScaleSettingCreateOrUpdate, @@ -24,6 +27,11 @@ func resourceArmAutoScaleSetting() *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": { @@ -344,6 +352,21 @@ func resourceArmAutoScaleSettingCreateOrUpdate(d *schema.ResourceData, meta inte name := d.Get("name").(string) resourceGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Auto Scale Setting %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_autoscale_setting", *resp.ID) + } + } + location := azureRMNormalizeLocation(d.Get("location").(string)) enabled := d.Get("enabled").(bool) targetResourceId := d.Get("target_resource_id").(string) @@ -371,7 +394,9 @@ func resourceArmAutoScaleSettingCreateOrUpdate(d *schema.ResourceData, meta inte Tags: expandedTags, } - if _, err := client.CreateOrUpdate(ctx, resourceGroup, name, parameters); err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + if _, err := client.CreateOrUpdate(waitCtx, resourceGroup, name, parameters); err != nil { return fmt.Errorf("Error creating AutoScale Setting %q (Resource Group %q): %+v", name, resourceGroup, err) } @@ -450,7 +475,9 @@ func resourceArmAutoScaleSettingDelete(d *schema.ResourceData, meta interface{}) resourceGroup := id.ResourceGroup name := id.Path["autoscalesettings"] - resp, err := client.Delete(ctx, resourceGroup, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resourceGroup, name) if err != nil { if !response.WasNotFound(resp.Response) { return fmt.Errorf("Error deleting AutoScale Setting %q (Resource Group %q): %+v", name, resourceGroup, err) diff --git a/azurerm/resource_arm_autoscale_setting_test.go b/azurerm/resource_arm_autoscale_setting_test.go index 352d28cc141d..db871ad3fab2 100644 --- a/azurerm/resource_arm_autoscale_setting_test.go +++ b/azurerm/resource_arm_autoscale_setting_test.go @@ -38,6 +38,31 @@ func TestAccAzureRMAutoScaleSetting_basic(t *testing.T) { }) } +func TestAccAzureRMAutoScaleSetting_requiresImport(t *testing.T) { + resourceName := "azurerm_autoscale_setting.test" + ri := acctest.RandInt() + rs := acctest.RandString(6) + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAutoScaleSettingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAutoScaleSetting_basic(ri, rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAutoScaleSettingExists(resourceName), + ), + }, + { + Config: testAccAzureRMAutoScaleSetting_requiresImport(ri, rs, location), + ExpectError: testRequiresImportError("azurerm_autoscale_setting"), + }, + }, + }) +} + func TestAccAzureRMAutoScaleSetting_multipleProfiles(t *testing.T) { resourceName := "azurerm_autoscale_setting.test" ri := acctest.RandInt() @@ -334,6 +359,50 @@ resource "azurerm_autoscale_setting" "test" { `, template, rInt) } +func testAccAzureRMAutoScaleSetting_requiresImport(rInt int, rString string, location string) string { + template := testAccAzureRMAutoScaleSetting_basic(rInt, rString, location) + return fmt.Sprintf(` +%s + +resource "azurerm_autoscale_setting" "import" { + name = "${azurerm_autoscale_setting.test.name}" + resource_group_name = "${azurerm_autoscale_setting.test.resource_group_name}" + location = "${azurerm_autoscale_setting.test.location}" + target_resource_id = "${azurerm_autoscale_setting.test.target_resource_id}" + + profile { + name = "metricRules" + + capacity { + default = 1 + minimum = 1 + maximum = 10 + } + + rule { + metric_trigger { + metric_name = "Percentage CPU" + metric_resource_id = "${azurerm_virtual_machine_scale_set.test.id}" + time_grain = "PT1M" + statistic = "Average" + time_window = "PT5M" + time_aggregation = "Average" + operator = "GreaterThan" + threshold = 75 + } + + scale_action { + direction = "Increase" + type = "ChangeCount" + value = 1 + cooldown = "PT1M" + } + } + } +} +`, template) +} + func testAccAzureRMAutoScaleSetting_multipleProfiles(rInt int, rString string, location string) string { template := testAccAzureRMAutoScaleSetting_template(rInt, rString, location) return fmt.Sprintf(` diff --git a/azurerm/resource_arm_availability_set.go b/azurerm/resource_arm_availability_set.go index bfa37d594f1e..b1f1f25dae4e 100644 --- a/azurerm/resource_arm_availability_set.go +++ b/azurerm/resource_arm_availability_set.go @@ -1,13 +1,16 @@ package azurerm import ( + "context" "fmt" "log" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-06-01/compute" "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 resourceArmAvailabilitySet() *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": { @@ -67,8 +75,23 @@ func resourceArmAvailabilitySetCreate(d *schema.ResourceData, meta interface{}) log.Printf("[INFO] preparing arguments for AzureRM Availability Set creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_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 Availability Set %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_availability_set", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) updateDomainCount := d.Get("platform_update_domain_count").(int) faultDomainCount := d.Get("platform_fault_domain_count").(int) managed := d.Get("managed").(bool) @@ -91,7 +114,9 @@ func resourceArmAvailabilitySetCreate(d *schema.ResourceData, meta interface{}) } } - resp, err := client.CreateOrUpdate(ctx, resGroup, name, availSet) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + resp, err := client.CreateOrUpdate(waitCtx, resGroup, name, availSet) if err != nil { return err } @@ -150,7 +175,9 @@ func resourceArmAvailabilitySetDelete(d *schema.ResourceData, meta interface{}) resGroup := id.ResourceGroup name := id.Path["availabilitySets"] - _, err = client.Delete(ctx, resGroup, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + _, err = client.Delete(waitCtx, resGroup, name) return err } diff --git a/azurerm/resource_arm_availability_set_test.go b/azurerm/resource_arm_availability_set_test.go index 9e819a8896ee..b6bc0d5d5469 100644 --- a/azurerm/resource_arm_availability_set_test.go +++ b/azurerm/resource_arm_availability_set_test.go @@ -33,6 +33,30 @@ func TestAccAzureRMAvailabilitySet_basic(t *testing.T) { }) } +func TestAccAzureRMAvailabilitySet_requiresImport(t *testing.T) { + resourceName := "azurerm_availability_set.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAvailabilitySetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAvailabilitySet_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAvailabilitySetExists(resourceName), + ), + }, + { + Config: testAccAzureRMAvailabilitySet_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_availability_set"), + }, + }, + }) +} + func TestAccAzureRMAvailabilitySet_disappears(t *testing.T) { resourceName := "azurerm_availability_set.test" ri := acctest.RandInt() @@ -230,6 +254,19 @@ resource "azurerm_availability_set" "test" { `, rInt, location, rInt) } +func testAccAzureRMAvailabilitySet_requiresImport(rInt int, location string) string { + template := testAccAzureRMAvailabilitySet_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_availability_set" "import" { + name = "${azurerm_availability_set.test.name}" + location = "${azurerm_availability_set.test.location}" + resource_group_name = "${azurerm_availability_set.test.resource_group_name}" +} +`, template) +} + func testAccAzureRMAvailabilitySet_withTags(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_azuread_application.go b/azurerm/resource_arm_azuread_application.go index c0e5647d3250..af1c869b91ef 100644 --- a/azurerm/resource_arm_azuread_application.go +++ b/azurerm/resource_arm_azuread_application.go @@ -1,8 +1,10 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" "github.com/hashicorp/terraform/helper/schema" @@ -18,6 +20,11 @@ func resourceArmActiveDirectoryApplication() *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": { @@ -72,9 +79,9 @@ func resourceArmActiveDirectoryApplicationCreate(d *schema.ResourceData, meta in client := meta.(*ArmClient).applicationsClient ctx := meta.(*ArmClient).StopContext + // NOTE: name isn't the Resource ID here, so we don't check it exists name := d.Get("name").(string) availableToOtherTenants := d.Get("available_to_other_tenants").(bool) - properties := graphrbac.ApplicationCreateParameters{ DisplayName: &name, Homepage: expandAzureRmActiveDirectoryApplicationHomepage(d, name), @@ -87,7 +94,9 @@ func resourceArmActiveDirectoryApplicationCreate(d *schema.ResourceData, meta in properties.Oauth2AllowImplicitFlow = utils.Bool(v.(bool)) } - app, err := client.Create(ctx, properties) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + app, err := client.Create(waitCtx, properties) if err != nil { return err } @@ -131,7 +140,9 @@ func resourceArmActiveDirectoryApplicationUpdate(d *schema.ResourceData, meta in properties.Oauth2AllowImplicitFlow = utils.Bool(oauth) } - _, err := client.Patch(ctx, d.Id(), properties) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutUpdate)) + defer cancel() + _, err := client.Patch(waitCtx, d.Id(), properties) if err != nil { return fmt.Errorf("Error patching Azure AD Application with ID %q: %+v", d.Id(), err) } @@ -177,6 +188,9 @@ func resourceArmActiveDirectoryApplicationDelete(d *schema.ResourceData, meta in client := meta.(*ArmClient).applicationsClient ctx := meta.(*ArmClient).StopContext + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + // in order to delete an application which is available to other tenants, we first have to disable this setting availableToOtherTenants := d.Get("available_to_other_tenants").(bool) if availableToOtherTenants { @@ -184,13 +198,13 @@ func resourceArmActiveDirectoryApplicationDelete(d *schema.ResourceData, meta in properties := graphrbac.ApplicationUpdateParameters{ AvailableToOtherTenants: utils.Bool(false), } - _, err := client.Patch(ctx, d.Id(), properties) + _, err := client.Patch(waitCtx, d.Id(), properties) if err != nil { return fmt.Errorf("Error patching Azure AD Application with ID %q: %+v", d.Id(), err) } } - resp, err := client.Delete(ctx, d.Id()) + resp, err := client.Delete(waitCtx, d.Id()) if err != nil { if !utils.ResponseWasNotFound(resp) { return fmt.Errorf("Error Deleting Azure AD Application with ID %q: %+v", d.Id(), err) diff --git a/azurerm/resource_arm_azuread_application_test.go b/azurerm/resource_arm_azuread_application_test.go index c3236f1fe73c..8c4054dcaef2 100644 --- a/azurerm/resource_arm_azuread_application_test.go +++ b/azurerm/resource_arm_azuread_application_test.go @@ -170,6 +170,17 @@ resource "azurerm_azuread_application" "test" { `, id) } +func testAccAzureRMActiveDirectoryApplication_requiresImport(id string) string { + template := testAccAzureRMActiveDirectoryApplication_basic(id) + return fmt.Sprintf(` +%s + +resource "azurerm_azuread_application" "import" { + name = "${azurerm_azuread_application.test.name}" +} +`, template) +} + func testAccAzureRMActiveDirectoryApplication_availableToOtherTenants(id string) string { return fmt.Sprintf(` resource "azurerm_azuread_application" "test" { diff --git a/azurerm/resource_arm_azuread_service_principal.go b/azurerm/resource_arm_azuread_service_principal.go index 71beabf650b3..2c069c26d711 100644 --- a/azurerm/resource_arm_azuread_service_principal.go +++ b/azurerm/resource_arm_azuread_service_principal.go @@ -1,12 +1,15 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -20,6 +23,11 @@ func resourceArmActiveDirectoryServicePrincipal() *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{ "application_id": { @@ -38,9 +46,32 @@ func resourceArmActiveDirectoryServicePrincipal() *schema.Resource { func resourceArmActiveDirectoryServicePrincipalCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).servicePrincipalsClient ctx := meta.(*ArmClient).StopContext + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() applicationId := d.Get("application_id").(string) + apps, err := client.ListComplete(waitCtx, "") + if err != nil { + return fmt.Errorf("Error checking for existence of Service Principal %q: %+v", applicationId, err) + } + + for apps.NotDone() { + a := apps.Value() + if a.AppID == nil || a.ObjectID == nil { + continue + } + + if *a.AppID == applicationId { + return tf.ImportAsExistsError("azurerm_azuread_service_principal", *a.ObjectID) + } + + e := apps.Next() + if e != nil { + return e + } + } + properties := graphrbac.ServicePrincipalCreateParameters{ AppID: utils.String(applicationId), // there's no way of retrieving this, and there's no way of changing it @@ -48,7 +79,7 @@ func resourceArmActiveDirectoryServicePrincipalCreate(d *schema.ResourceData, me AccountEnabled: utils.Bool(true), } - app, err := client.Create(ctx, properties) + app, err := client.Create(waitCtx, properties) if err != nil { return fmt.Errorf("Error creating Service Principal %q: %+v", applicationId, err) } @@ -90,7 +121,9 @@ func resourceArmActiveDirectoryServicePrincipalDelete(d *schema.ResourceData, me ctx := meta.(*ArmClient).StopContext applicationId := d.Id() - app, err := client.Delete(ctx, applicationId) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + app, err := client.Delete(waitCtx, applicationId) if err != nil { if !response.WasNotFound(app.Response) { return fmt.Errorf("Error deleting Service Principal ID %q: %+v", applicationId, err) diff --git a/azurerm/resource_arm_azuread_service_principal_password.go b/azurerm/resource_arm_azuread_service_principal_password.go index f1c3bdf3f57c..bf2265e88c9c 100644 --- a/azurerm/resource_arm_azuread_service_principal_password.go +++ b/azurerm/resource_arm_azuread_service_principal_password.go @@ -1,6 +1,7 @@ package azurerm import ( + "context" "fmt" "log" "strings" @@ -10,6 +11,7 @@ import ( "github.com/Azure/go-autorest/autorest/date" "github.com/hashicorp/go-uuid" "github.com/hashicorp/terraform/helper/schema" + "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/utils" ) @@ -22,6 +24,10 @@ func resourceArmActiveDirectoryServicePrincipalPassword() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Schema: map[string]*schema.Schema{ "service_principal_id": { @@ -68,6 +74,9 @@ func resourceArmActiveDirectoryServicePrincipalPasswordCreate(d *schema.Resource client := meta.(*ArmClient).servicePrincipalsClient ctx := meta.(*ArmClient).StopContext + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + objectId := d.Get("service_principal_id").(string) value := d.Get("value").(string) // errors will be handled by the validation @@ -100,13 +109,23 @@ func resourceArmActiveDirectoryServicePrincipalPasswordCreate(d *schema.Resource azureRMLockByName(objectId, servicePrincipalResourceName) defer azureRMUnlockByName(objectId, servicePrincipalResourceName) - existingCredentials, err := client.ListPasswordCredentials(ctx, objectId) + existingCredentials, err := client.ListPasswordCredentials(waitCtx, objectId) if err != nil { return fmt.Errorf("Error Listing Password Credentials for Service Principal %q: %+v", objectId, err) } updatedCredentials := make([]graphrbac.PasswordCredential, 0) if existingCredentials.Value != nil { + for _, v := range *existingCredentials.Value { + if v.KeyID == nil { + continue + } + + if *v.KeyID == keyId { + return tf.ImportAsExistsError("azurerm_azuread_service_principal_password", fmt.Sprintf("%s/%s", objectId, keyId)) + } + } + updatedCredentials = *existingCredentials.Value } @@ -115,7 +134,7 @@ func resourceArmActiveDirectoryServicePrincipalPasswordCreate(d *schema.Resource parameters := graphrbac.PasswordCredentialsUpdateParameters{ Value: &updatedCredentials, } - _, err = client.UpdatePasswordCredentials(ctx, objectId, parameters) + _, err = client.UpdatePasswordCredentials(waitCtx, objectId, parameters) if err != nil { return fmt.Errorf("Error creating Password Credential %q for Service Principal %q: %+v", keyId, objectId, err) } @@ -190,6 +209,8 @@ func resourceArmActiveDirectoryServicePrincipalPasswordRead(d *schema.ResourceDa func resourceArmActiveDirectoryServicePrincipalPasswordDelete(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).servicePrincipalsClient ctx := meta.(*ArmClient).StopContext + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() id := strings.Split(d.Id(), "/") if len(id) != 2 { @@ -203,7 +224,7 @@ func resourceArmActiveDirectoryServicePrincipalPasswordDelete(d *schema.Resource defer azureRMUnlockByName(objectId, servicePrincipalResourceName) // ensure the parent Service Principal exists - servicePrincipal, err := client.Get(ctx, objectId) + servicePrincipal, err := client.Get(waitCtx, objectId) if err != nil { // the parent Service Principal was removed - skip it if utils.ResponseWasNotFound(servicePrincipal.Response) { @@ -213,7 +234,7 @@ func resourceArmActiveDirectoryServicePrincipalPasswordDelete(d *schema.Resource return fmt.Errorf("Error retrieving Service Principal ID %q: %+v", objectId, err) } - existing, err := client.ListPasswordCredentials(ctx, objectId) + existing, err := client.ListPasswordCredentials(waitCtx, objectId) if err != nil { return fmt.Errorf("Error Listing Password Credentials for Service Principal with Object ID %q: %+v", objectId, err) } @@ -232,7 +253,7 @@ func resourceArmActiveDirectoryServicePrincipalPasswordDelete(d *schema.Resource parameters := graphrbac.PasswordCredentialsUpdateParameters{ Value: &updatedCredentials, } - _, err = client.UpdatePasswordCredentials(ctx, objectId, parameters) + _, err = client.UpdatePasswordCredentials(waitCtx, objectId, parameters) if err != nil { return fmt.Errorf("Error removing Password %q from Service Principal %q: %+v", keyId, objectId, err) } diff --git a/azurerm/resource_arm_azuread_service_principal_test.go b/azurerm/resource_arm_azuread_service_principal_test.go index cea7ebf6624b..1fce3a99d47a 100644 --- a/azurerm/resource_arm_azuread_service_principal_test.go +++ b/azurerm/resource_arm_azuread_service_principal_test.go @@ -32,6 +32,29 @@ func TestAccAzureRMActiveDirectoryServicePrincipal_basic(t *testing.T) { }) } +func TestAccAzureRMActiveDirectoryServicePrincipal_requiresImport(t *testing.T) { + resourceName := "azurerm_azuread_service_principal.test" + id := uuid.New().String() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMActiveDirectoryServicePrincipalDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMActiveDirectoryServicePrincipal_basic(id), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMActiveDirectoryServicePrincipalExists(resourceName), + ), + }, + { + Config: testAccAzureRMActiveDirectoryServicePrincipal_requiresImport(id), + ExpectError: testRequiresImportError("azurerm_azuread_service_principal"), + }, + }, + }) +} + func testCheckAzureRMActiveDirectoryServicePrincipalExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] @@ -89,3 +112,14 @@ resource "azurerm_azuread_service_principal" "test" { } `, id) } + +func testAccAzureRMActiveDirectoryServicePrincipal_requiresImport(id string) string { + template := testAccAzureRMActiveDirectoryServicePrincipal_basic(id) + return fmt.Sprintf(` +%s + +resource "azurerm_azuread_service_principal" "import" { + application_id = "${azurerm_azuread_service_principal.test.application_id}" +} +`, template) +} diff --git a/azurerm/resource_arm_cdn_endpoint.go b/azurerm/resource_arm_cdn_endpoint.go index 90b0ebdeb5b3..a0d65fcc8406 100644 --- a/azurerm/resource_arm_cdn_endpoint.go +++ b/azurerm/resource_arm_cdn_endpoint.go @@ -1,13 +1,16 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/cdn/mgmt/2017-10-12/cdn" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -20,6 +23,11 @@ func resourceArmCdnEndpoint() *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": { @@ -189,10 +197,23 @@ func resourceArmCdnEndpointCreate(d *schema.ResourceData, meta interface{}) erro log.Printf("[INFO] preparing arguments for Azure ARM CDN EndPoint creation.") - name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resourceGroup := d.Get("resource_group_name").(string) profileName := d.Get("profile_name").(string) + name := d.Get("name").(string) + + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, profileName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of CDN Endpoint %q (Profile %q / Resource Group %q): %+v", name, profileName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_cdn_endpoint", *resp.ID) + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) httpAllowed := d.Get("is_http_allowed").(bool) httpsAllowed := d.Get("is_https_allowed").(bool) compressionEnabled := d.Get("is_compression_enabled").(bool) @@ -246,7 +267,9 @@ func resourceArmCdnEndpointCreate(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("Error creating CDN Endpoint %q (Profile %q / Resource Group %q): %+v", name, profileName, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for CDN Endpoint %q (Profile %q / Resource Group %q) to finish creating: %+v", name, profileName, resourceGroup, err) } @@ -312,7 +335,9 @@ func resourceArmCdnEndpointUpdate(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("Error updating CDN Endpoint %q (Profile %q / Resource Group %q): %s", name, profileName, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, endpointsClient.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutUpdate)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, endpointsClient.Client) if err != nil { return fmt.Errorf("Error waiting for the CDN Endpoint %q (Profile %q / Resource Group %q) to finish updating: %+v", name, profileName, resourceGroup, err) } @@ -408,7 +433,9 @@ func resourceArmCdnEndpointDelete(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("Error deleting CDN Endpoint %q (Profile %q / Resource Group %q): %+v", name, profileName, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if response.WasNotFound(future.Response()) { return nil diff --git a/azurerm/resource_arm_cdn_endpoint_test.go b/azurerm/resource_arm_cdn_endpoint_test.go index c676882ba0e9..44755da5d13f 100644 --- a/azurerm/resource_arm_cdn_endpoint_test.go +++ b/azurerm/resource_arm_cdn_endpoint_test.go @@ -30,6 +30,30 @@ func TestAccAzureRMCdnEndpoint_basic(t *testing.T) { }) } +func TestAccAzureRMCdnEndpoint_requiresImport(t *testing.T) { + resourceName := "azurerm_cdn_endpoint.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMCdnEndpointDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMCdnEndpoint_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMCdnEndpointExists(resourceName), + ), + }, + { + Config: testAccAzureRMCdnEndpoint_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_cdn_endpoint"), + }, + }, + }) +} + func TestAccAzureRMCdnEndpoint_disappears(t *testing.T) { resourceName := "azurerm_cdn_endpoint.test" ri := acctest.RandInt() @@ -340,6 +364,27 @@ resource "azurerm_cdn_endpoint" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMCdnEndpoint_requiresImport(rInt int, location string) string { + template := testAccAzureRMCdnEndpoint_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_cdn_endpoint" "import" { + name = "${azurerm_cdn_endpoint.test.name}" + profile_name = "${azurerm_cdn_endpoint.test.profile_name}" + location = "${azurerm_cdn_endpoint.test.location}" + resource_group_name = "${azurerm_cdn_endpoint.test.resource_group_name}" + + origin { + name = "acceptanceTestCdnOrigin1" + host_name = "www.example.com" + https_port = 443 + http_port = 80 + } +} +`, template) +} + func testAccAzureRMCdnEndpoint_hostHeader(rInt int, domain string, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_cdn_profile.go b/azurerm/resource_arm_cdn_profile.go index d72dfd00c3a9..f15fd9d8965e 100644 --- a/azurerm/resource_arm_cdn_profile.go +++ b/azurerm/resource_arm_cdn_profile.go @@ -1,13 +1,16 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/cdn/mgmt/2017-10-12/cdn" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -20,6 +23,11 @@ func resourceArmCdnProfile() *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": { @@ -59,8 +67,21 @@ func resourceArmCdnProfileCreate(d *schema.ResourceData, meta interface{}) error log.Printf("[INFO] preparing arguments for Azure ARM CDN Profile creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(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 CDN Profile %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_cdn_profile", *resp.ID) + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) sku := d.Get("sku").(string) tags := d.Get("tags").(map[string]interface{}) @@ -77,7 +98,9 @@ func resourceArmCdnProfileCreate(d *schema.ResourceData, meta interface{}) error return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -116,7 +139,9 @@ func resourceArmCdnProfileUpdate(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("Error issuing update request for CDN Profile %q (Resource Group %q): %+v", name, resourceGroup, 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 fmt.Errorf("Error waiting for the update of CDN Profile %q (Resource Group %q) to commplete: %+v", name, resourceGroup, err) } @@ -178,7 +203,9 @@ func resourceArmCdnProfileDelete(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("Error issuing delete request for CDN Profile %q (Resource Group %q): %+v", name, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if response.WasNotFound(future.Response()) { return nil diff --git a/azurerm/resource_arm_cdn_profile_test.go b/azurerm/resource_arm_cdn_profile_test.go index 2a7e1fc46362..ea87eeb708a7 100644 --- a/azurerm/resource_arm_cdn_profile_test.go +++ b/azurerm/resource_arm_cdn_profile_test.go @@ -80,6 +80,29 @@ func TestAccAzureRMCdnProfile_basic(t *testing.T) { }) } +func TestAccAzureRMCdnProfile_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMCdnProfileDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMCdnProfile_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMCdnProfileExists("azurerm_cdn_profile.test"), + ), + }, + { + Config: testAccAzureRMCdnProfile_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_cdn_profile"), + }, + }, + }) +} + func TestAccAzureRMCdnProfile_withTags(t *testing.T) { resourceName := "azurerm_cdn_profile.test" ri := acctest.RandInt() @@ -281,6 +304,20 @@ resource "azurerm_cdn_profile" "test" { `, rInt, location, rInt) } +func testAccAzureRMCdnProfile_requiresImport(rInt int, location string) string { + template := testAccAzureRMCdnProfile_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_cdn_profile" "import" { + name = "${azurerm_cdn_profile.test.name}" + location = "${azurerm_cdn_profile.test.location}" + resource_group_name = "${azurerm_cdn_profile.test.resource_group_name}" + sku = "${azurerm_cdn_profile.test.sku}" +} +`, template) +} + func testAccAzureRMCdnProfile_withTags(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_container_group.go b/azurerm/resource_arm_container_group.go index 15e14b2cc5b2..0ef1d260bcdf 100644 --- a/azurerm/resource_arm_container_group.go +++ b/azurerm/resource_arm_container_group.go @@ -1,13 +1,16 @@ package azurerm import ( + "context" "fmt" "log" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2018-04-01/containerinstance" "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" ) @@ -19,6 +22,10 @@ func resourceArmContainerGroup() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Schema: map[string]*schema.Schema{ "name": { @@ -238,11 +245,24 @@ func resourceArmContainerGroup() *schema.Resource { } func resourceArmContainerGroupCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).containerGroupsClient ctx := meta.(*ArmClient).StopContext - containerGroupsClient := meta.(*ArmClient).containerGroupsClient resGroup := d.Get("resource_group_name").(string) name := d.Get("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 Container Group %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_container_group", *resp.ID) + } + location := azureRMNormalizeLocation(d.Get("location").(string)) OSType := d.Get("os_type").(string) IPAddressType := d.Get("ip_address_type").(string) @@ -271,12 +291,14 @@ func resourceArmContainerGroupCreate(d *schema.ResourceData, meta interface{}) e containerGroup.ContainerGroupProperties.IPAddress.DNSNameLabel = &dnsNameLabel } - _, err := containerGroupsClient.CreateOrUpdate(ctx, resGroup, name, containerGroup) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + _, err = client.CreateOrUpdate(waitCtx, resGroup, name, containerGroup) if err != nil { return err } - read, err := containerGroupsClient.Get(ctx, resGroup, name) + read, err := client.Get(ctx, resGroup, name) if err != nil { return err } @@ -356,7 +378,9 @@ func resourceArmContainerGroupDelete(d *schema.ResourceData, meta interface{}) e resourceGroup := id.ResourceGroup name := id.Path["containerGroups"] - resp, err := client.Delete(ctx, resourceGroup, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resourceGroup, name) if err != nil { if !utils.ResponseWasNotFound(resp.Response) { return fmt.Errorf("Error deleting Container Group %q (Resource Group %q): %+v", name, resourceGroup, err) diff --git a/azurerm/resource_arm_container_group_test.go b/azurerm/resource_arm_container_group_test.go index 4889930c97d8..2eb00499a543 100644 --- a/azurerm/resource_arm_container_group_test.go +++ b/azurerm/resource_arm_container_group_test.go @@ -119,6 +119,30 @@ func TestAccAzureRMContainerGroup_linuxBasic(t *testing.T) { }) } +func TestAccAzureRMContainerGroup_requiresImport(t *testing.T) { + resourceName := "azurerm_container_group.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMContainerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMContainerGroup_linuxBasic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMContainerGroupExists(resourceName), + ), + }, + { + Config: testAccAzureRMContainerGroup_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_container_group"), + }, + }, + }) +} + func TestAccAzureRMContainerGroup_linuxBasicUpdate(t *testing.T) { resourceName := "azurerm_container_group.test" ri := acctest.RandInt() @@ -287,6 +311,33 @@ resource "azurerm_container_group" "test" { `, ri, location, ri) } +func testAccAzureRMContainerGroup_requiresImport(ri int, location string) string { + template := testAccAzureRMContainerGroup_linuxBasic(ri, location) + return fmt.Sprintf(` +%s + +resource "azurerm_container_group" "import" { + name = "${azurerm_container_group.test.name}" + location = "${azurerm_container_group.test.location}" + resource_group_name = "${azurerm_container_group.test.resource_group_name}" + ip_address_type = "${azurerm_container_group.test.ip_address_type}" + os_type = "${azurerm_container_group.test.os_type}" + + container { + name = "hw" + image = "microsoft/aci-helloworld:latest" + cpu = "0.5" + memory = "0.5" + port = "80" + } + + tags { + environment = "Testing" + } +} +`, template) +} + func testAccAzureRMContainerGroup_imageRegistryCredentials(ri int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_container_registry.go b/azurerm/resource_arm_container_registry.go index b60882cb298c..32d229dde52f 100644 --- a/azurerm/resource_arm_container_registry.go +++ b/azurerm/resource_arm_container_registry.go @@ -1,9 +1,11 @@ package azurerm import ( + "context" "fmt" "log" "regexp" + "time" "strings" @@ -11,6 +13,7 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -25,6 +28,11 @@ func resourceArmContainerRegistry() *schema.Resource { }, MigrateState: resourceAzureRMContainerRegistryMigrateState, SchemaVersion: 2, + 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": { @@ -111,6 +119,19 @@ func resourceArmContainerRegistryCreate(d *schema.ResourceData, meta interface{} resourceGroup := d.Get("resource_group_name").(string) name := d.Get("name").(string) + + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Container Registry %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_container_registry", *resp.ID) + } + location := azureRMNormalizeLocation(d.Get("location").(string)) sku := d.Get("sku").(string) adminUserEnabled := d.Get("admin_enabled").(bool) @@ -147,7 +168,9 @@ func resourceArmContainerRegistryCreate(d *schema.ResourceData, meta interface{} return fmt.Errorf("Error creating Container Registry %q (Resource Group %q): %+v", name, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for creation of Container Registry %q (Resource Group %q): %+v", name, resourceGroup, err) } @@ -208,7 +231,9 @@ func resourceArmContainerRegistryUpdate(d *schema.ResourceData, meta interface{} return fmt.Errorf("Error updating Container Registry %q (Resource Group %q): %+v", name, resourceGroup, 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 fmt.Errorf("Error waiting for update of Container Registry %q (Resource Group %q): %+v", name, resourceGroup, err) } @@ -305,7 +330,9 @@ func resourceArmContainerRegistryDelete(d *schema.ResourceData, meta interface{} return fmt.Errorf("Error issuing Azure ARM delete request of Container Registry '%s': %+v", name, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if response.WasNotFound(future.Response()) { return nil diff --git a/azurerm/resource_arm_container_registry_test.go b/azurerm/resource_arm_container_registry_test.go index 76712201e1eb..f30e92dd6a9d 100644 --- a/azurerm/resource_arm_container_registry_test.go +++ b/azurerm/resource_arm_container_registry_test.go @@ -106,6 +106,29 @@ func TestAccAzureRMContainerRegistry_basicBasic(t *testing.T) { }) } +func TestAccAzureRMContainerRegistry_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMContainerRegistryDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMContainerRegistry_basicManaged(ri, location, "Basic"), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMContainerRegistryExists("azurerm_container_registry.test"), + ), + }, + { + Config: testAccAzureRMContainerRegistry_requiresImport(ri, location, "Basic"), + ExpectError: testRequiresImportError("azurerm_container_registry"), + }, + }, + }) +} + func TestAccAzureRMContainerRegistry_basicStandard(t *testing.T) { ri := acctest.RandInt() config := testAccAzureRMContainerRegistry_basicManaged(ri, testLocation(), "Standard") @@ -292,6 +315,20 @@ resource "azurerm_container_registry" "test" { `, rInt, location, rInt, sku) } +func testAccAzureRMContainerRegistry_requiresImport(rInt int, location string, sku string) string { + template := testAccAzureRMContainerRegistry_basicManaged(rInt, location, sku) + return fmt.Sprintf(` +%s + +resource "azurerm_container_registry" "import" { + name = "${azurerm_container_registry.test.name}" + resource_group_name = "${azurerm_container_registry.test.resource_group_name}" + location = "${azurerm_container_registry.test.location}" + sku = "${azurerm_container_registry.test.sku}" +} +`, template) +} + func testAccAzureRMContainerRegistry_basicUnmanaged(rInt int, rStr string, location string, sku string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_container_service.go b/azurerm/resource_arm_container_service.go index eabfa2c9f864..e8cf5c2b92c0 100644 --- a/azurerm/resource_arm_container_service.go +++ b/azurerm/resource_arm_container_service.go @@ -1,6 +1,7 @@ package azurerm import ( + "context" "fmt" "log" @@ -12,15 +13,24 @@ import ( "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/resource" "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" ) func resourceArmContainerService() *schema.Resource { return &schema.Resource{ - Create: resourceArmContainerServiceCreate, + Create: resourceArmContainerServiceCreateUpdate, Read: resourceArmContainerServiceRead, - Update: resourceArmContainerServiceCreate, + Update: resourceArmContainerServiceCreateUpdate, Delete: resourceArmContainerServiceDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Hour * 1), + Update: schema.DefaultTimeout(time.Hour * 1), + Delete: schema.DefaultTimeout(time.Hour * 1), + }, Schema: map[string]*schema.Schema{ "name": { @@ -181,16 +191,30 @@ func resourceArmContainerService() *schema.Resource { } } -func resourceArmContainerServiceCreate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*ArmClient) - containerServiceClient := client.containerServicesClient +func resourceArmContainerServiceCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).containerServicesClient + ctx := meta.(*ArmClient).StopContext - log.Printf("[INFO] preparing arguments for Azure ARM Container Service creation.") + log.Printf("[INFO] preparing arguments for Container Service creation.") resGroup := d.Get("resource_group_name").(string) name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(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 Container Service %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_container_service", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) orchestrationPlatform := d.Get("orchestration_platform").(string) masterProfile := expandAzureRmContainerServiceMasterProfile(d) @@ -220,31 +244,33 @@ func resourceArmContainerServiceCreate(d *schema.ResourceData, meta interface{}) parameters.ServicePrincipalProfile = servicePrincipalProfile } - ctx := meta.(*ArmClient).StopContext - _, error := containerServiceClient.CreateOrUpdate(ctx, resGroup, name, parameters) - if error != nil { - return error + _, err := client.CreateOrUpdate(ctx, resGroup, name, parameters) + if err != nil { + return err } - read, err := containerServiceClient.Get(ctx, resGroup, name) + read, err := client.Get(ctx, resGroup, name) if err != nil { return err } if read.ID == nil { - return fmt.Errorf("Cannot read Container Service %s (resource group %s) ID", name, resGroup) + return fmt.Errorf("Cannot read Container Service %q (resource group %q) ID", name, resGroup) } - log.Printf("[DEBUG] Waiting for Container Service (%s) to become available", d.Get("name")) + log.Printf("[DEBUG] Waiting for Container Service %q (Resource Group %q) to become available", name, resGroup) + timeout := d.Timeout(tf.TimeoutForCreateUpdate(d)) + waitCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() stateConf := &resource.StateChangeConf{ Pending: []string{"Updating", "Creating"}, Target: []string{"Succeeded"}, - Refresh: containerServiceStateRefreshFunc(client, resGroup, name), - Timeout: 30 * time.Minute, + Refresh: containerServiceStateRefreshFunc(waitCtx, client, resGroup, name), + Timeout: timeout, MinTimeout: 15 * time.Second, } if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf("Error waiting for Container Service (%s) to become available: %s", d.Get("name"), err) + return fmt.Errorf("Error waiting for Container Service %q (Resource Group %q) to become available: %s", name, resGroup, err) } d.SetId(*read.ID) @@ -279,25 +305,36 @@ func resourceArmContainerServiceRead(d *schema.ResourceData, meta interface{}) e d.Set("location", azureRMNormalizeLocation(*location)) } - d.Set("orchestration_platform", string(resp.Properties.OrchestratorProfile.OrchestratorType)) + if props := resp.Properties; props != nil { + if profile := props.OrchestratorProfile; profile != nil { + d.Set("orchestration_platform", string(profile.OrchestratorType)) + } - masterProfiles := flattenAzureRmContainerServiceMasterProfile(*resp.Properties.MasterProfile) - d.Set("master_profile", &masterProfiles) + linuxProfile := flattenAzureRmContainerServiceLinuxProfile(props.LinuxProfile) + if err := d.Set("linux_profile", linuxProfile); err != nil { + return fmt.Errorf("Error flattening `linux_profile`: %+v", err) + } - linuxProfile := flattenAzureRmContainerServiceLinuxProfile(*resp.Properties.LinuxProfile) - d.Set("linux_profile", &linuxProfile) + masterProfiles := flattenAzureRmContainerServiceMasterProfile(props.MasterProfile) + if err := d.Set("master_profile", masterProfiles); err != nil { + return fmt.Errorf("Error flattening `master_profile`: %+v", err) + } - agentPoolProfiles := flattenAzureRmContainerServiceAgentPoolProfiles(resp.Properties.AgentPoolProfiles) - d.Set("agent_pool_profile", &agentPoolProfiles) + agentPoolProfiles := flattenAzureRmContainerServiceAgentPoolProfiles(props.AgentPoolProfiles) + if err := d.Set("agent_pool_profile", agentPoolProfiles); err != nil { + return fmt.Errorf("Error flattening `agent_pool_profile`: %+v", err) + } - servicePrincipal := flattenAzureRmContainerServiceServicePrincipalProfile(resp.Properties.ServicePrincipalProfile) - if servicePrincipal != nil { - d.Set("service_principal", servicePrincipal) - } + servicePrincipal := flattenAzureRmContainerServiceServicePrincipalProfile(props.ServicePrincipalProfile) + if err := d.Set("service_principal", servicePrincipal); err != nil { + return fmt.Errorf("Error flattening `service_principal`: %+v", err) + } + + diagnosticProfile := flattenAzureRmContainerServiceDiagnosticsProfile(props.DiagnosticsProfile) + if err := d.Set("diagnostics_profile", diagnosticProfile); err != nil { + return fmt.Errorf("Error flattening `diagnostics_profile`: %+v", err) + } - diagnosticProfile := flattenAzureRmContainerServiceDiagnosticsProfile(resp.Properties.DiagnosticsProfile) - if diagnosticProfile != nil { - d.Set("diagnostics_profile", diagnosticProfile) } flattenAndSetTags(d, resp.Tags) @@ -306,8 +343,8 @@ func resourceArmContainerServiceRead(d *schema.ResourceData, meta interface{}) e } func resourceArmContainerServiceDelete(d *schema.ResourceData, meta interface{}) error { - client := meta.(*ArmClient) - containerServiceClient := client.containerServicesClient + containerServiceClient := meta.(*ArmClient).containerServicesClient + ctx := meta.(*ArmClient).StopContext id, err := parseAzureResourceID(d.Id()) if err != nil { @@ -316,113 +353,159 @@ func resourceArmContainerServiceDelete(d *schema.ResourceData, meta interface{}) resGroup := id.ResourceGroup name := id.Path["containerServices"] - ctx := meta.(*ArmClient).StopContext future, err := containerServiceClient.Delete(ctx, resGroup, name) - if err != nil { - return fmt.Errorf("Error issuing Azure ARM delete request of Container Service '%s': %s", name, err) + return fmt.Errorf("Error deleting Container Service %q (Resource Group %q): %+v", name, resGroup, err) } - err = future.WaitForCompletionRef(ctx, containerServiceClient.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, containerServiceClient.Client) if err != nil { return err } + return nil } -func flattenAzureRmContainerServiceMasterProfile(profile containerservice.MasterProfile) *schema.Set { - masterProfiles := &schema.Set{ +func flattenAzureRmContainerServiceMasterProfile(input *containerservice.MasterProfile) *schema.Set { + masterProfiles := schema.Set{ F: resourceAzureRMContainerServiceMasterProfileHash, } - masterProfile := make(map[string]interface{}, 3) + if input != nil { + masterProfile := make(map[string]interface{}, 0) + + if input.Count != nil { + masterProfile["count"] = int(*input.Count) + } + + if input.DNSPrefix != nil { + masterProfile["dns_prefix"] = *input.DNSPrefix + } - masterProfile["count"] = int(*profile.Count) - masterProfile["dns_prefix"] = *profile.DNSPrefix - masterProfile["fqdn"] = *profile.Fqdn + if input.Fqdn != nil { + masterProfile["fqdn"] = *input.Fqdn + } - masterProfiles.Add(masterProfile) + masterProfiles.Add(masterProfile) + } - return masterProfiles + return &masterProfiles } -func flattenAzureRmContainerServiceLinuxProfile(profile containerservice.LinuxProfile) *schema.Set { - profiles := &schema.Set{ +func flattenAzureRmContainerServiceLinuxProfile(input *containerservice.LinuxProfile) *schema.Set { + profiles := schema.Set{ F: resourceAzureRMContainerServiceLinuxProfilesHash, } - values := map[string]interface{}{} + if input != nil { + sshKeys := &schema.Set{ + F: resourceAzureRMContainerServiceLinuxProfilesSSHKeysHash, + } - sshKeys := &schema.Set{ - F: resourceAzureRMContainerServiceLinuxProfilesSSHKeysHash, - } - for _, ssh := range *profile.SSH.PublicKeys { - keys := map[string]interface{}{} - keys["key_data"] = *ssh.KeyData - sshKeys.Add(keys) - } + values := map[string]interface{}{} + if config := input.SSH; config != nil { + if keys := config.PublicKeys; keys != nil { + for _, ssh := range *config.PublicKeys { + if ssh.KeyData == nil { + continue + } + + keys := map[string]interface{}{} + keys["key_data"] = *ssh.KeyData + sshKeys.Add(keys) + } + } + } - values["admin_username"] = *profile.AdminUsername - values["ssh_key"] = sshKeys - profiles.Add(values) + if input.AdminUsername != nil { + values["admin_username"] = *input.AdminUsername + } - return profiles + values["ssh_key"] = sshKeys + profiles.Add(values) + } + + return &profiles } func flattenAzureRmContainerServiceAgentPoolProfiles(profiles *[]containerservice.AgentPoolProfile) *schema.Set { - agentPoolProfiles := &schema.Set{ + agentPoolProfiles := schema.Set{ F: resourceAzureRMContainerServiceAgentPoolProfilesHash, } for _, profile := range *profiles { agentPoolProfile := map[string]interface{}{} - agentPoolProfile["count"] = int(*profile.Count) - agentPoolProfile["dns_prefix"] = *profile.DNSPrefix - agentPoolProfile["fqdn"] = *profile.Fqdn - agentPoolProfile["name"] = *profile.Name + + if profile.Count != nil { + agentPoolProfile["count"] = int(*profile.Count) + } + + if profile.DNSPrefix != nil { + agentPoolProfile["dns_prefix"] = *profile.DNSPrefix + } + + if profile.Fqdn != nil { + agentPoolProfile["fqdn"] = *profile.Fqdn + } + + if profile.Name != nil { + agentPoolProfile["name"] = *profile.Name + } + agentPoolProfile["vm_size"] = string(profile.VMSize) agentPoolProfiles.Add(agentPoolProfile) } - return agentPoolProfiles + return &agentPoolProfiles } func flattenAzureRmContainerServiceServicePrincipalProfile(profile *containerservice.ServicePrincipalProfile) *schema.Set { - - if profile == nil { - return nil - } - - servicePrincipalProfiles := &schema.Set{ + servicePrincipalProfiles := schema.Set{ F: resourceAzureRMContainerServiceServicePrincipalProfileHash, } + if profile == nil { + return &servicePrincipalProfiles + } values := map[string]interface{}{} - values["client_id"] = *profile.ClientID + if profile.ClientID != nil { + values["client_id"] = *profile.ClientID + } + if profile.Secret != nil { values["client_secret"] = *profile.Secret } servicePrincipalProfiles.Add(values) - return servicePrincipalProfiles + return &servicePrincipalProfiles } func flattenAzureRmContainerServiceDiagnosticsProfile(profile *containerservice.DiagnosticsProfile) *schema.Set { - diagnosticProfiles := &schema.Set{ + diagnosticProfiles := schema.Set{ F: resourceAzureRMContainerServiceDiagnosticProfilesHash, } + if profile == nil { + return &diagnosticProfiles + } values := map[string]interface{}{} - values["enabled"] = *profile.VMDiagnostics.Enabled - if profile.VMDiagnostics.StorageURI != nil { - values["storage_uri"] = *profile.VMDiagnostics.StorageURI + if diags := profile.VMDiagnostics; diags != nil { + if diags.Enabled != nil { + values["enabled"] = *diags.Enabled + } + if diags.StorageURI != nil { + values["storage_uri"] = *diags.StorageURI + } + + diagnosticProfiles.Add(values) } - diagnosticProfiles.Add(values) - return diagnosticProfiles + return &diagnosticProfiles } func expandAzureRmContainerServiceDiagnostics(d *schema.ResourceData) containerservice.DiagnosticsProfile { @@ -529,10 +612,9 @@ func expandAzureRmContainerServiceAgentProfiles(d *schema.ResourceData) []contai return profiles } -func containerServiceStateRefreshFunc(client *ArmClient, resourceGroupName string, containerServiceName string) resource.StateRefreshFunc { +func containerServiceStateRefreshFunc(ctx context.Context, client containerservice.ContainerServicesClient, resourceGroupName string, containerServiceName string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - ctx := client.StopContext - res, err := client.containerServicesClient.Get(ctx, resourceGroupName, containerServiceName) + res, err := client.Get(ctx, resourceGroupName, containerServiceName) if err != nil { return nil, "", fmt.Errorf("Error issuing read request in containerServiceStateRefreshFunc to Azure ARM for Container Service '%s' (RG: '%s'): %s", containerServiceName, resourceGroupName, err) } diff --git a/azurerm/resource_arm_container_service_test.go b/azurerm/resource_arm_container_service_test.go index 2f633b66d1ee..8dfd4619dd88 100644 --- a/azurerm/resource_arm_container_service_test.go +++ b/azurerm/resource_arm_container_service_test.go @@ -77,6 +77,7 @@ func TestAccAzureRMContainerService_agentProfilePoolCountValidation(t *testing.T } func TestAccAzureRMContainerService_dcosBasic(t *testing.T) { + resourceName := "azurerm_container_service.test" ri := acctest.RandInt() config := testAccAzureRMContainerService_dcosBasic(ri, testLocation()) @@ -88,14 +89,44 @@ func TestAccAzureRMContainerService_dcosBasic(t *testing.T) { { Config: config, Check: resource.ComposeTestCheckFunc( - testCheckAzureRMContainerServiceExists("azurerm_container_service.test"), + testCheckAzureRMContainerServiceExists(resourceName), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMContainerService_requiresImport(t *testing.T) { + resourceName := "azurerm_container_service.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMContainerServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMContainerService_dcosBasic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMContainerServiceExists(resourceName), + ), + }, + { + Config: testAccAzureRMContainerService_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_container_service"), + }, }, }) } func TestAccAzureRMContainerService_kubernetesBasic(t *testing.T) { + resourceName := "azurerm_container_service.test" ri := acctest.RandInt() clientId := os.Getenv("ARM_CLIENT_ID") clientSecret := os.Getenv("ARM_CLIENT_SECRET") @@ -109,14 +140,21 @@ func TestAccAzureRMContainerService_kubernetesBasic(t *testing.T) { { Config: config, Check: resource.ComposeTestCheckFunc( - testCheckAzureRMContainerServiceExists("azurerm_container_service.test"), + testCheckAzureRMContainerServiceExists(resourceName), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"service_principal"}, + }, }, }) } func TestAccAzureRMContainerService_kubernetesComplete(t *testing.T) { + resourceName := "azurerm_container_service.test" ri := acctest.RandInt() clientId := os.Getenv("ARM_CLIENT_ID") clientSecret := os.Getenv("ARM_CLIENT_SECRET") @@ -130,14 +168,21 @@ func TestAccAzureRMContainerService_kubernetesComplete(t *testing.T) { { Config: config, Check: resource.ComposeTestCheckFunc( - testCheckAzureRMContainerServiceExists("azurerm_container_service.test"), + testCheckAzureRMContainerServiceExists(resourceName), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"service_principal"}, + }, }, }) } func TestAccAzureRMContainerService_swarmBasic(t *testing.T) { + resourceName := "azurerm_container_service.test" ri := acctest.RandInt() config := testAccAzureRMContainerService_swarmBasic(ri, testLocation()) @@ -149,9 +194,14 @@ func TestAccAzureRMContainerService_swarmBasic(t *testing.T) { { Config: config, Check: resource.ComposeTestCheckFunc( - testCheckAzureRMContainerServiceExists("azurerm_container_service.test"), + testCheckAzureRMContainerServiceExists(resourceName), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -195,6 +245,43 @@ resource "azurerm_container_service" "test" { } `, rInt, location, rInt, rInt, rInt, rInt) } +func testAccAzureRMContainerService_requiresImport(rInt int, location string) string { + template := testAccAzureRMContainerService_dcosBasic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_container_service" "import" { + name = "${azurerm_container_service.test.name}" + location = "${azurerm_container_service.test.location}" + resource_group_name = "${azurerm_container_service.test.resource_group_name}" + orchestration_platform = "DCOS" + + master_profile { + count = 1 + dns_prefix = "acctestmaster%d" + } + + linux_profile { + admin_username = "acctestuser%d" + + ssh_key { + key_data = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCqaZoyiz1qbdOQ8xEf6uEu1cCwYowo5FHtsBhqLoDnnp7KUTEBN+L2NxRIfQ781rxV6Iq5jSav6b2Q8z5KiseOlvKA/RF2wqU0UPYqQviQhLmW6THTpmrv/YkUCuzxDpsH7DUDhZcwySLKVVe0Qm3+5N2Ta6UYH3lsDf9R9wTP2K/+vAnflKebuypNlmocIvakFWoZda18FOmsOoIVXQ8HWFNCuw9ZCunMSN62QGamCe3dL5cXlkgHYv7ekJE15IA9aOJcM7e90oeTqo+7HTcWfdu0qQqPWY5ujyMw/llas8tsXY85LFqRnr3gJ02bAscjc477+X+j/gkpFoN1QEmt terraform@demo.tld" + } + } + + agent_pool_profile { + name = "default" + count = 1 + dns_prefix = "acctestagent%d" + vm_size = "Standard_F2" + } + + diagnostics_profile { + enabled = false + } +} +`, template, rInt, rInt, rInt) +} func testAccAzureRMContainerService_kubernetesBasic(rInt int, clientId string, clientSecret string, location string) string { return fmt.Sprintf(` diff --git a/azurerm/resource_arm_cosmos_db_account.go b/azurerm/resource_arm_cosmos_db_account.go index eef0d0507855..c9972693d21f 100644 --- a/azurerm/resource_arm_cosmos_db_account.go +++ b/azurerm/resource_arm_cosmos_db_account.go @@ -9,6 +9,7 @@ import ( "time" "github.com/Azure/azure-sdk-for-go/services/cosmos-db/mgmt/2015-04-08/documentdb" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/resource" @@ -28,6 +29,11 @@ func resourceArmCosmosDBAccount() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Hour * 1), + Update: schema.DefaultTimeout(time.Hour * 1), + Delete: schema.DefaultTimeout(time.Hour * 1), + }, Schema: map[string]*schema.Schema{ "name": { @@ -46,7 +52,6 @@ func resourceArmCosmosDBAccount() *schema.Resource { "tags": tagsSchema(), - //resource fields "offer_type": { Type: schema.TypeString, Required: true, @@ -205,7 +210,6 @@ func resourceArmCosmosDBAccount() *schema.Resource { Set: resourceAzureRMCosmosDBAccountCapabilitiesHash, }, - //computed "endpoint": { Type: schema.TypeString, Computed: true, @@ -269,14 +273,20 @@ func resourceArmCosmosDBAccountCreate(d *schema.ResourceData, meta interface{}) log.Printf("[INFO] preparing arguments for AzureRM Cosmos DB Account creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resourceGroup := d.Get("resource_group_name").(string) - tags := d.Get("tags").(map[string]interface{}) - kind := d.Get("kind").(string) - offerType := d.Get("offer_type").(string) - ipRangeFilter := d.Get("ip_range_filter").(string) - enableAutomaticFailover := d.Get("enable_automatic_failover").(bool) + // first check if there's one in this subscription requiring import + existing, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for the existence of Cosmos DB Account %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + if existing.ID != nil { + return tf.ImportAsExistsError("azurerm_cosmosdb_account", *existing.ID) + } + + // then check it's globally unique r, err := client.CheckNameExists(ctx, name) if err != nil { return fmt.Errorf("Error checking if CosmosDB Account %q already exists (Resource Group %q): %+v", name, resourceGroup, err) @@ -285,6 +295,13 @@ func resourceArmCosmosDBAccountCreate(d *schema.ResourceData, meta interface{}) return fmt.Errorf("CosmosDB Account %s already exists, please import the resource via terraform import", name) } + location := azureRMNormalizeLocation(d.Get("location").(string)) + tags := d.Get("tags").(map[string]interface{}) + kind := d.Get("kind").(string) + offerType := d.Get("offer_type").(string) + ipRangeFilter := d.Get("ip_range_filter").(string) + enableAutomaticFailover := d.Get("enable_automatic_failover").(bool) + //hacky, todo fix up once deprecated field 'failover_policy' is removed var geoLocations []documentdb.Location if _, ok := d.GetOk("geo_location"); ok { @@ -316,7 +333,7 @@ func resourceArmCosmosDBAccountCreate(d *schema.ResourceData, meta interface{}) Tags: expandTags(tags), } - resp, err := resourceArmCosmosDBAccountApiUpsert(client, ctx, resourceGroup, name, account) + resp, err := resourceArmCosmosDBAccountApiUpsert(ctx, client, d, resourceGroup, name, account) if err != nil { return fmt.Errorf("Error creating CosmosDB Account %q (Resource Group %q): %+v", name, resourceGroup, err) } @@ -400,7 +417,7 @@ func resourceArmCosmosDBAccountUpdate(d *schema.ResourceData, meta interface{}) Tags: expandTags(tags), } - if _, err := resourceArmCosmosDBAccountApiUpsert(client, ctx, resourceGroup, name, account); err != nil { + if _, err := resourceArmCosmosDBAccountApiUpsert(ctx, client, d, resourceGroup, name, account); err != nil { return fmt.Errorf("Error updating CosmosDB Account %q properties (Resource Group %q): %+v", name, resourceGroup, err) } @@ -437,14 +454,14 @@ func resourceArmCosmosDBAccountUpdate(d *schema.ResourceData, meta interface{}) } account.DatabaseAccountCreateUpdateProperties.Locations = &locationsUnchanged - if _, err := resourceArmCosmosDBAccountApiUpsert(client, ctx, resourceGroup, name, account); err != nil { + if _, err := resourceArmCosmosDBAccountApiUpsert(ctx, client, d, resourceGroup, name, account); err != nil { return fmt.Errorf("Error removing CosmosDB Account %q renamed locations (Resource Group %q): %+v", name, resourceGroup, err) } } //add any new/renamed locations account.DatabaseAccountCreateUpdateProperties.Locations = &newLocations - upsertResponse, err := resourceArmCosmosDBAccountApiUpsert(client, ctx, resourceGroup, name, account) + upsertResponse, err := resourceArmCosmosDBAccountApiUpsert(ctx, client, d, resourceGroup, name, account) if err != nil { return fmt.Errorf("Error updating CosmosDB Account %q locations (Resource Group %q): %+v", name, resourceGroup, err) } @@ -588,7 +605,7 @@ func resourceArmCosmosDBAccountDelete(d *schema.ResourceData, meta interface{}) stateConf := &resource.StateChangeConf{ Pending: []string{"Deleting"}, Target: []string{"NotFound"}, - Timeout: 60 * time.Minute, + Timeout: d.Timeout(schema.TimeoutDelete), MinTimeout: 30 * time.Second, Refresh: func() (interface{}, string, error) { @@ -611,7 +628,7 @@ func resourceArmCosmosDBAccountDelete(d *schema.ResourceData, meta interface{}) return nil } -func resourceArmCosmosDBAccountApiUpsert(client documentdb.DatabaseAccountsClient, ctx context.Context, resourceGroup string, name string, account documentdb.DatabaseAccountCreateUpdateParameters) (*documentdb.DatabaseAccount, error) { +func resourceArmCosmosDBAccountApiUpsert(ctx context.Context, client documentdb.DatabaseAccountsClient, d *schema.ResourceData, resourceGroup string, name string, account documentdb.DatabaseAccountCreateUpdateParameters) (*documentdb.DatabaseAccount, error) { future, err := client.CreateOrUpdate(ctx, resourceGroup, name, account) if err != nil { return nil, fmt.Errorf("Error creating/updating CosmosDB Account %q (Resource Group %q): %+v", name, resourceGroup, err) @@ -626,7 +643,7 @@ func resourceArmCosmosDBAccountApiUpsert(client documentdb.DatabaseAccountsClien stateConf := &resource.StateChangeConf{ Pending: []string{"Creating", "Updating", "Deleting"}, Target: []string{"Succeeded"}, - Timeout: 60 * time.Minute, + Timeout: d.Timeout(tf.TimeoutForCreateUpdate(d)), MinTimeout: 30 * time.Second, Delay: 30 * time.Second, // required because it takes some time before the 'creating' location shows up Refresh: func() (interface{}, string, error) { diff --git a/azurerm/resource_arm_cosmos_db_account_test.go b/azurerm/resource_arm_cosmos_db_account_test.go index b98e7f1c1a56..833ed54c80d2 100644 --- a/azurerm/resource_arm_cosmos_db_account_test.go +++ b/azurerm/resource_arm_cosmos_db_account_test.go @@ -62,6 +62,31 @@ func testSweepCosmosDBAccount(region string) error { return nil } +func TestAccAzureRMCosmosDBAccount_requiresImport(t *testing.T) { + resourceName := "azurerm_cosmosdb_account.test" + ri := acctest.RandInt() + location := testLocation() + consistencyLevel := string(documentdb.Eventual) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMCosmosDBAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMCosmosDBAccount_basic(ri, location, consistencyLevel), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMCosmosDBAccountExists(resourceName), + ), + }, + { + Config: testAccAzureRMCosmosDBAccount_requiresImport(ri, location, consistencyLevel), + ExpectError: testRequiresImportError("azurerm_cosmosdb_account"), + }, + }, + }) +} + //consistency func TestAccAzureRMCosmosDBAccount_eventualConsistency(t *testing.T) { ri := acctest.RandInt() @@ -73,7 +98,7 @@ func TestAccAzureRMCosmosDBAccount_eventualConsistency(t *testing.T) { CheckDestroy: testCheckAzureRMCosmosDBAccountDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.Eventual), "", ""), + Config: testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.Eventual)), Check: resource.ComposeAggregateTestCheckFunc( checkAccAzureRMCosmosDBAccount_basic(resourceName, testLocation(), string(documentdb.Eventual), 1), ), @@ -97,7 +122,7 @@ func TestAccAzureRMCosmosDBAccount_session(t *testing.T) { CheckDestroy: testCheckAzureRMCosmosDBAccountDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.Session), "", ""), + Config: testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.Session)), Check: resource.ComposeAggregateTestCheckFunc( checkAccAzureRMCosmosDBAccount_basic(resourceName, testLocation(), string(documentdb.Session), 1), ), @@ -121,7 +146,7 @@ func TestAccAzureRMCosmosDBAccount_strong(t *testing.T) { CheckDestroy: testCheckAzureRMCosmosDBAccountDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.Strong), "", ""), + Config: testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.Strong)), Check: resource.ComposeAggregateTestCheckFunc( checkAccAzureRMCosmosDBAccount_basic(resourceName, testLocation(), string(documentdb.Strong), 1), ), @@ -145,7 +170,7 @@ func TestAccAzureRMCosmosDBAccount_consistentPrefix(t *testing.T) { CheckDestroy: testCheckAzureRMCosmosDBAccountDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.ConsistentPrefix), "", ""), + Config: testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.ConsistentPrefix)), Check: resource.ComposeAggregateTestCheckFunc( checkAccAzureRMCosmosDBAccount_basic(resourceName, testLocation(), string(documentdb.ConsistentPrefix), 1), ), @@ -169,7 +194,7 @@ func TestAccAzureRMCosmosDBAccount_boundedStaleness(t *testing.T) { CheckDestroy: testCheckAzureRMCosmosDBAccountDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.BoundedStaleness), "", ""), + Config: testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.BoundedStaleness)), Check: resource.ComposeAggregateTestCheckFunc( checkAccAzureRMCosmosDBAccount_basic(resourceName, testLocation(), string(documentdb.BoundedStaleness), 1), ), @@ -219,11 +244,11 @@ func TestAccAzureRMCosmosDBAccount_consistency_change(t *testing.T) { CheckDestroy: testCheckAzureRMCosmosDBAccountDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.Session), "", ""), + Config: testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.Session)), Check: checkAccAzureRMCosmosDBAccount_basic(resourceName, testLocation(), string(documentdb.Session), 1), }, { - Config: testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.BoundedStaleness), "", ""), + Config: testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.BoundedStaleness)), Check: checkAccAzureRMCosmosDBAccount_basic(resourceName, testLocation(), string(documentdb.BoundedStaleness), 1), }, { @@ -322,7 +347,7 @@ func TestAbcAzureRMCosmosDBAccount_updatePropertiesAndLocation(t *testing.T) { CheckDestroy: testCheckAzureRMCosmosDBAccountDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.Session), "", ""), + Config: testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.Session)), Check: checkAccAzureRMCosmosDBAccount_basic(resourceName, testLocation(), string(documentdb.Session), 1), }, { @@ -366,7 +391,7 @@ func TestAccAzureRMCosmosDBAccount_geoReplicated_customId(t *testing.T) { func TestAccAzureRMCosmosDBAccount_geoReplicated_add_remove(t *testing.T) { ri := acctest.RandInt() resourceName := "azurerm_cosmosdb_account.test" - configBasic := testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.BoundedStaleness), "", "") + configBasic := testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.BoundedStaleness)) configReplicated := testAccAzureRMCosmosDBAccount_geoReplicated_customId(ri, testLocation(), testAltLocation()) resource.Test(t, resource.TestCase{ @@ -436,7 +461,7 @@ func TestAccAzureRMCosmosDBAccount_complete(t *testing.T) { CheckDestroy: testCheckAzureRMCosmosDBAccountDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.Session), "", ""), + Config: testAccAzureRMCosmosDBAccount_basic(ri, testLocation(), string(documentdb.Session)), Check: checkAccAzureRMCosmosDBAccount_basic(resourceName, testLocation(), string(documentdb.Session), 1), }, { @@ -512,7 +537,7 @@ func testCheckAzureRMCosmosDBAccountExists(name string) resource.TestCheckFunc { } } -func testAccAzureRMCosmosDBAccount_basic(rInt int, location string, consistency string, consistencyOptions string, addtional string) string { +func testAccAzureRMCosmosDBAccount_basic(rInt int, location string, consistency string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { name = "acctestRG-%d" @@ -527,85 +552,246 @@ resource "azurerm_cosmosdb_account" "test" { consistency_policy { consistency_level = "%s" - %s } geo_location { location = "${azurerm_resource_group.test.location}" failover_priority = 0 } +} +`, rInt, location, rInt, consistency) +} +func testAccAzureRMCosmosDBAccount_requiresImport(rInt int, location string, consistency string) string { + template := testAccAzureRMCosmosDBAccount_basic(rInt, location, consistency) + return fmt.Sprintf(` %s +resource "azurerm_cosmosdb_account" "import" { + name = "${azurerm_cosmosdb_account.test.name}" + location = "${azurerm_cosmosdb_account.test.location}" + resource_group_name = "${azurerm_cosmosdb_account.test.resource_group_name}" + offer_type = "Standard" + + consistency_policy { + consistency_level = "${azurerm_cosmosdb_account.test.consistency_policy.0.consistency_level}" + } + + geo_location { + location = "${azurerm_resource_group.test.location}" + failover_priority = 0 + } } -`, rInt, location, rInt, consistency, consistencyOptions, addtional) +`, template) } func testAccAzureRMCosmosDBAccount_boundedStaleness_complete(rInt int, location string) string { - return testAccAzureRMCosmosDBAccount_basic(rInt, location, string(documentdb.BoundedStaleness), ` - max_interval_in_seconds = 10 - max_staleness_prefix = 200 -`, "") + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_cosmosdb_account" "test" { + name = "acctest-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + offer_type = "Standard" + + consistency_policy { + consistency_level = "BoundedStaleness" + max_interval_in_seconds = 10 + max_staleness_prefix = 200 + } + + geo_location { + location = "${azurerm_resource_group.test.location}" + failover_priority = 0 + } +} +`, rInt, location, rInt) } func testAccAzureRMCosmosDBAccount_mongoDB(rInt int, location string) string { - return testAccAzureRMCosmosDBAccount_basic(rInt, location, string(documentdb.BoundedStaleness), "", ` - kind = "MongoDB" - `) + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_cosmosdb_account" "test" { + name = "acctest-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + offer_type = "Standard" + kind = "MongoDB" + + consistency_policy { + consistency_level = "BoundedStaleness" + } + + geo_location { + location = "${azurerm_resource_group.test.location}" + failover_priority = 0 + } +} +`, rInt, location, rInt) } func testAccAzureRMCosmosDBAccount_gremlin(rInt int, location string) string { - return testAccAzureRMCosmosDBAccount_basic(rInt, location, string(documentdb.BoundedStaleness), "", ` - kind = "GlobalDocumentDB" - - capabilities = { - name = "EnableGremlin" - } - `) + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_cosmosdb_account" "test" { + name = "acctest-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + offer_type = "Standard" + kind = "GlobalDocumentDB" + + consistency_policy { + consistency_level = "BoundedStaleness" + } + + geo_location { + location = "${azurerm_resource_group.test.location}" + failover_priority = 0 + } + + capabilities { + name = "EnableGremlin" + } +} +`, rInt, location, rInt) } func testAccAzureRMCosmosDBAccount_table(rInt int, location string) string { - return testAccAzureRMCosmosDBAccount_basic(rInt, location, string(documentdb.BoundedStaleness), "", ` - kind = "GlobalDocumentDB" + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_cosmosdb_account" "test" { + name = "acctest-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + offer_type = "Standard" + kind = "GlobalDocumentDB" + + consistency_policy { + consistency_level = "BoundedStaleness" + } + + geo_location { + location = "${azurerm_resource_group.test.location}" + failover_priority = 0 + } - capabilities = { - name = "EnableTable" - } - `) + capabilities { + name = "EnableTable" + } +} +`, rInt, location, rInt) } func testAccAzureRMCosmosDBAccount_geoReplicated(rInt int, location string, altLocation string) string { - return testAccAzureRMCosmosDBAccount_basic(rInt, location, string(documentdb.BoundedStaleness), "", fmt.Sprintf(` - geo_location { - location = "%s" - failover_priority = 1 - } + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_cosmosdb_account" "test" { + name = "acctest-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + offer_type = "Standard" + kind = "GlobalDocumentDB" + + consistency_policy { + consistency_level = "BoundedStaleness" + } + + geo_location { + location = "${azurerm_resource_group.test.location}" + failover_priority = 0 + } - `, altLocation)) + geo_location { + location = "%s" + failover_priority = 1 + } +} +`, rInt, location, rInt, altLocation) } func testAccAzureRMCosmosDBAccount_geoReplicated_customId(rInt int, location string, altLocation string) string { - return testAccAzureRMCosmosDBAccount_basic(rInt, location, string(documentdb.BoundedStaleness), "", fmt.Sprintf(` - geo_location { - prefix = "acctest-%d-custom-id" - location = "%s" - failover_priority = 1 - } + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_cosmosdb_account" "test" { + name = "acctest-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + offer_type = "Standard" + kind = "GlobalDocumentDB" + + consistency_policy { + consistency_level = "BoundedStaleness" + } + + geo_location { + location = "${azurerm_resource_group.test.location}" + failover_priority = 0 + } - `, rInt, altLocation)) + geo_location { + prefix = "acctest-%d-custom-id" + location = "%s" + failover_priority = 1 + } +} +`, rInt, location, rInt, rInt, altLocation) } func testAccAzureRMCosmosDBAccount_complete(rInt int, location string, altLocation string) string { - return testAccAzureRMCosmosDBAccount_basic(rInt, location, string(documentdb.BoundedStaleness), "", fmt.Sprintf(` - ip_range_filter = "104.42.195.92,40.76.54.131,52.176.6.30,52.169.50.45,52.187.184.26,10.20.0.0/16" - enable_automatic_failover = true - - geo_location { - prefix = "acctest-%d-custom-id" - location = "%s" - failover_priority = 1 - } - `, rInt, altLocation)) + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_cosmosdb_account" "test" { + name = "acctest-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + offer_type = "Standard" + ip_range_filter = "104.42.195.92,40.76.54.131,52.176.6.30,52.169.50.45,52.187.184.26,10.20.0.0/16" + enable_automatic_failover = true + + consistency_policy { + consistency_level = "BoundedStaleness" + } + + geo_location { + location = "${azurerm_resource_group.test.location}" + failover_priority = 0 + } + + geo_location { + prefix = "acctest-%d-custom-id" + location = "%s" + failover_priority = 1 + } +} +`, rInt, location, rInt, rInt, altLocation) } func checkAccAzureRMCosmosDBAccount_basic(resourceName string, location string, consistency string, locationCount int) resource.TestCheckFunc { diff --git a/azurerm/resource_arm_data_lake_analytics_account.go b/azurerm/resource_arm_data_lake_analytics_account.go index f101bfe32eae..164219b637d6 100644 --- a/azurerm/resource_arm_data_lake_analytics_account.go +++ b/azurerm/resource_arm_data_lake_analytics_account.go @@ -1,10 +1,13 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/datalake/analytics/mgmt/2016-11-01/account" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" @@ -21,10 +24,14 @@ func resourceArmDataLakeAnalyticsAccount() *schema.Resource { Read: resourceArmDateLakeAnalyticsAccountRead, Update: resourceArmDateLakeAnalyticsAccountUpdate, Delete: resourceArmDateLakeAnalyticsAccountDelete, - 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": { @@ -73,13 +80,26 @@ func resourceArmDateLakeAnalyticsAccountCreate(d *schema.ResourceData, meta inte ctx := meta.(*ArmClient).StopContext name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resourceGroup := d.Get("resource_group_name").(string) + + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Data Lake Analytics Account %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_data_lake_analytics_account", *resp.ID) + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) storeAccountName := d.Get("default_store_account_name").(string) tier := d.Get("tier").(string) tags := d.Get("tags").(map[string]interface{}) - log.Printf("[INFO] preparing arguments for Azure ARM Date Lake Store creation %q (Resource Group %q)", name, resourceGroup) + log.Printf("[INFO] preparing arguments for Data Lake Analytics Account creation %q (Resource Group %q)", name, resourceGroup) dateLakeAnalyticsAccount := account.CreateDataLakeAnalyticsAccountParameters{ Location: &location, @@ -100,7 +120,9 @@ func resourceArmDateLakeAnalyticsAccountCreate(d *schema.ResourceData, meta inte return fmt.Errorf("Error issuing create request for Data Lake Analytics Account %q (Resource Group %q): %+v", name, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error creating Data Lake Analytics Account %q (Resource Group %q): %+v", name, resourceGroup, err) } @@ -145,7 +167,9 @@ func resourceArmDateLakeAnalyticsAccountUpdate(d *schema.ResourceData, meta inte return fmt.Errorf("Error issuing update request for Data Lake Analytics Account %q (Resource Group %q): %+v", name, resourceGroup, 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 fmt.Errorf("Error waiting for the update of Data Lake Analytics Account %q (Resource Group %q) to commplete: %+v", name, resourceGroup, err) } @@ -210,7 +234,9 @@ func resourceArmDateLakeAnalyticsAccountDelete(d *schema.ResourceData, meta inte return fmt.Errorf("Error issuing delete request for Data Lake Analytics Account %q (Resource Group %q): %+v", name, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if response.WasNotFound(future.Response()) { return nil diff --git a/azurerm/resource_arm_data_lake_analytics_account_test.go b/azurerm/resource_arm_data_lake_analytics_account_test.go index 30dd8303df92..7f2fbe5102cc 100644 --- a/azurerm/resource_arm_data_lake_analytics_account_test.go +++ b/azurerm/resource_arm_data_lake_analytics_account_test.go @@ -36,6 +36,30 @@ func TestAccAzureRMDataLakeAnalyticsAccount_basic(t *testing.T) { }) } +func TestAccAzureRMDataLakeAnalyticsAccount_requiresImport(t *testing.T) { + resourceName := "azurerm_data_lake_analytics_account.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDataLakeAnalyticsAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDataLakeAnalyticsAccount_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDataLakeAnalyticsAccountExists(resourceName), + ), + }, + { + Config: testAccAzureRMDataLakeAnalyticsAccount_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_data_lake_analytics_account"), + }, + }, + }) +} + func TestAccAzureRMDataLakeAnalyticsAccount_tier(t *testing.T) { resourceName := "azurerm_data_lake_analytics_account.test" ri := acctest.RandInt() @@ -155,15 +179,28 @@ func testAccAzureRMDataLakeAnalyticsAccount_basic(rInt int, location string) str %s resource "azurerm_data_lake_analytics_account" "test" { - name = "acctest%s" - resource_group_name = "${azurerm_resource_group.test.name}" - location = "${azurerm_resource_group.test.location}" - + name = "acctest%s" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" default_store_account_name = "${azurerm_data_lake_store.test.name}" } `, testAccAzureRMDataLakeStore_basic(rInt, location), strconv.Itoa(rInt)[0:15]) } +func testAccAzureRMDataLakeAnalyticsAccount_requiresImport(rInt int, location string) string { + template := testAccAzureRMDataLakeAnalyticsAccount_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_data_lake_analytics_account" "import" { + name = "${azurerm_data_lake_analytics_account.test.name}" + resource_group_name = "${azurerm_data_lake_analytics_account.test.resource_group_name}" + location = "${azurerm_data_lake_analytics_account.test.location}" + default_store_account_name = "${azurerm_data_lake_analytics_account.test.default_store_account_name}" +} +`, template) +} + func testAccAzureRMDataLakeAnalyticsAccount_tier(rInt int, location string) string { return fmt.Sprintf(` %s diff --git a/azurerm/resource_arm_data_lake_analytics_firewall_rule.go b/azurerm/resource_arm_data_lake_analytics_firewall_rule.go index 8a4d01c11e4e..39c201da5abb 100644 --- a/azurerm/resource_arm_data_lake_analytics_firewall_rule.go +++ b/azurerm/resource_arm_data_lake_analytics_firewall_rule.go @@ -1,10 +1,13 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/datalake/analytics/mgmt/2016-11-01/account" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/hashicorp/terraform/helper/schema" @@ -20,10 +23,14 @@ func resourceArmDataLakeAnalyticsFirewallRule() *schema.Resource { Read: resourceArmDateLakeAnalyticsFirewallRuleRead, Update: resourceArmDateLakeAnalyticsFirewallRuleCreateUpdate, Delete: resourceArmDateLakeAnalyticsFirewallRuleDelete, - Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 5), + Update: schema.DefaultTimeout(time.Minute * 5), + Delete: schema.DefaultTimeout(time.Minute * 5), + }, Schema: map[string]*schema.Schema{ "name": { @@ -64,10 +71,25 @@ func resourceArmDateLakeAnalyticsFirewallRuleCreateUpdate(d *schema.ResourceData name := d.Get("name").(string) accountName := d.Get("account_name").(string) resourceGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, accountName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Date Lake Analytics Firewall Rule %q (Account %q / Resource Group %q): %+v", name, accountName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_data_lake_analytics_firewall_rule", *resp.ID) + } + } + startIPAddress := d.Get("start_ip_address").(string) endIPAddress := d.Get("end_ip_address").(string) - log.Printf("[INFO] preparing arguments for Date Lake Analytics Firewall Rule creation %q (Resource Group %q)", name, resourceGroup) + log.Printf("[INFO] preparing arguments for Date Lake Analytics Firewall Rule creation %q (Account %q / Resource Group %q)", name, accountName, resourceGroup) dateLakeStore := account.CreateOrUpdateFirewallRuleParameters{ CreateOrUpdateFirewallRuleProperties: &account.CreateOrUpdateFirewallRuleProperties{ @@ -76,9 +98,11 @@ func resourceArmDateLakeAnalyticsFirewallRuleCreateUpdate(d *schema.ResourceData }, } - _, err := client.CreateOrUpdate(ctx, resourceGroup, accountName, name, dateLakeStore) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, resourceGroup, accountName, name, dateLakeStore) if err != nil { - return fmt.Errorf("Error issuing create request for Data Lake Analytics %q (Resource Group %q): %+v", name, resourceGroup, err) + return fmt.Errorf("Error issuing create request for Data Lake Analytics %q (Account %q / Resource Group %q): %+v", name, accountName, resourceGroup, err) } read, err := client.Get(ctx, resourceGroup, accountName, name) @@ -141,7 +165,9 @@ func resourceArmDateLakeAnalyticsFirewallRuleDelete(d *schema.ResourceData, meta accountName := id.Path["accounts"] name := id.Path["firewallRules"] - resp, err := client.Delete(ctx, resourceGroup, accountName, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resourceGroup, accountName, name) if err != nil { if response.WasNotFound(resp.Response) { return nil diff --git a/azurerm/resource_arm_data_lake_analytics_firewall_rule_test.go b/azurerm/resource_arm_data_lake_analytics_firewall_rule_test.go index c5e044612cd6..c92466e9cc81 100644 --- a/azurerm/resource_arm_data_lake_analytics_firewall_rule_test.go +++ b/azurerm/resource_arm_data_lake_analytics_firewall_rule_test.go @@ -5,10 +5,11 @@ import ( "net/http" "testing" + "strconv" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" - "strconv" ) func TestAccAzureRMDataLakeAnalyticsFirewallRule_basic(t *testing.T) { @@ -39,6 +40,32 @@ func TestAccAzureRMDataLakeAnalyticsFirewallRule_basic(t *testing.T) { }) } +func TestAccAzureRMDataLakeAnalyticsFirewallRule_requiresImport(t *testing.T) { + resourceName := "azurerm_data_lake_analytics_firewall_rule.test" + ri := acctest.RandInt() + location := testLocation() + startIP := "1.1.1.1" + endIP := "2.2.2.2" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDataLakeAnalyticsFirewallRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDataLakeAnalyticsFirewallRule_basic(ri, location, startIP, endIP), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDataLakeAnalyticsFirewallRuleExists(resourceName), + ), + }, + { + Config: testAccAzureRMDataLakeAnalyticsFirewallRule_requiresImport(ri, location, startIP, endIP), + ExpectError: testRequiresImportError("azurerm_data_lake_analytics_firewall_rule"), + }, + }, + }) +} + func TestAccAzureRMDataLakeAnalyticsFirewallRule_update(t *testing.T) { resourceName := "azurerm_data_lake_analytics_firewall_rule.test" ri := acctest.RandInt() @@ -155,6 +182,7 @@ func testCheckAzureRMDataLakeAnalyticsFirewallRuleDestroy(s *terraform.State) er } func testAccAzureRMDataLakeAnalyticsFirewallRule_basic(rInt int, location, startIP, endIP string) string { + rs := strconv.Itoa(rInt)[0:15] return fmt.Sprintf(` resource "azurerm_resource_group" "test" { name = "acctestRG-%[1]d" @@ -182,5 +210,20 @@ resource "azurerm_data_lake_analytics_firewall_rule" "test" { start_ip_address = "%[4]s" end_ip_address = "%[5]s" } -`, rInt, location, strconv.Itoa(rInt)[0:15], startIP, endIP) +`, rInt, location, rs, startIP, endIP) +} + +func testAccAzureRMDataLakeAnalyticsFirewallRule_requiresImport(rInt int, location, startIP, endIP string) string { + template := testAccAzureRMDataLakeAnalyticsFirewallRule_basic(rInt, location, startIP, endIP) + return fmt.Sprintf(` +%s + +resource "azurerm_data_lake_analytics_firewall_rule" "import" { + name = "${azurerm_data_lake_analytics_firewall_rule.test.name}" + account_name = "${azurerm_data_lake_analytics_firewall_rule.test.account_name}" + resource_group_name = "${azurerm_data_lake_analytics_firewall_rule.test.resource_group_name}" + start_ip_address = "${azurerm_data_lake_analytics_firewall_rule.test.start_ip_address}" + end_ip_address = "${azurerm_data_lake_analytics_firewall_rule.test.end_ip_address}" +} +`, template) } diff --git a/azurerm/resource_arm_data_lake_store.go b/azurerm/resource_arm_data_lake_store.go index 687ba8cc671a..da0bfbb94a33 100644 --- a/azurerm/resource_arm_data_lake_store.go +++ b/azurerm/resource_arm_data_lake_store.go @@ -1,8 +1,10 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/datalake/store/mgmt/2016-11-01/account" "github.com/hashicorp/terraform/helper/schema" @@ -10,19 +12,25 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" "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/utils" ) +// TODO: rename this resource to `azurerm_data_lake_store_account` func resourceArmDataLakeStore() *schema.Resource { return &schema.Resource{ Create: resourceArmDateLakeStoreCreate, Read: resourceArmDateLakeStoreRead, Update: resourceArmDateLakeStoreUpdate, Delete: resourceArmDateLakeStoreDelete, - 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": { @@ -112,8 +120,22 @@ func resourceArmDateLakeStoreCreate(d *schema.ResourceData, meta interface{}) er ctx := meta.(*ArmClient).StopContext name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resourceGroup := d.Get("resource_group_name").(string) + + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Data Lake Store Account %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + // TODO: rename this resource in a future release + return tf.ImportAsExistsError("azurerm_data_lake_store", *resp.ID) + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) tier := d.Get("tier").(string) encryptionState := account.EncryptionState(d.Get("encryption_state").(string)) @@ -124,7 +146,7 @@ func resourceArmDateLakeStoreCreate(d *schema.ResourceData, meta interface{}) er log.Printf("[INFO] preparing arguments for Data Lake Store creation %q (Resource Group %q)", name, resourceGroup) - dateLakeStore := account.CreateDataLakeStoreAccountParameters{ + dataLakeStore := account.CreateDataLakeStoreAccountParameters{ Location: &location, Tags: expandTags(tags), CreateDataLakeStoreAccountProperties: &account.CreateDataLakeStoreAccountProperties{ @@ -138,12 +160,14 @@ func resourceArmDateLakeStoreCreate(d *schema.ResourceData, meta interface{}) er }, } - future, err := client.Create(ctx, resourceGroup, name, dateLakeStore) + future, err := client.Create(ctx, resourceGroup, name, dataLakeStore) if err != nil { return fmt.Errorf("Error issuing create request for Data Lake Store %q (Resource Group %q): %+v", name, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error creating Data Lake Store %q (Resource Group %q): %+v", name, resourceGroup, err) } @@ -186,7 +210,9 @@ func resourceArmDateLakeStoreUpdate(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("Error issuing update request for Data Lake Store %q (Resource Group %q): %+v", name, resourceGroup, 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 fmt.Errorf("Error waiting for the update of Data Lake Store %q (Resource Group %q) to commplete: %+v", name, resourceGroup, err) } @@ -260,7 +286,9 @@ func resourceArmDateLakeStoreDelete(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("Error issuing delete request for Data Lake Store %q (Resource Group %q): %+v", name, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if response.WasNotFound(future.Response()) { return nil diff --git a/azurerm/resource_arm_data_lake_store_file.go b/azurerm/resource_arm_data_lake_store_file.go index 84172915c922..1420986ca8bf 100644 --- a/azurerm/resource_arm_data_lake_store_file.go +++ b/azurerm/resource_arm_data_lake_store_file.go @@ -2,16 +2,20 @@ package azurerm import ( "bytes" + "context" "fmt" "io/ioutil" "log" + "net/http" "net/url" "os" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/datalake/store/2016-11-01/filesystem" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -25,6 +29,10 @@ func resourceArmDataLakeStoreFile() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Schema: map[string]*schema.Schema{ "account_name": { @@ -57,6 +65,19 @@ func resourceArmDataLakeStoreFileCreate(d *schema.ResourceData, meta interface{} accountName := d.Get("account_name").(string) remoteFilePath := d.Get("remote_file_path").(string) + + // first check if there's one in this subscription requiring import + resp, err := client.GetFileStatus(ctx, accountName, remoteFilePath, utils.Bool(true)) + if resp.StatusCode == http.StatusOK { + return tf.ImportAsExistsError("azurerm_data_lake_store_file", remoteFilePath) + } + + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Data Lake Store File %q (Account %q): %+v", remoteFilePath, accountName, err) + } + } + localFilePath := d.Get("local_file_path").(string) file, err := os.Open(localFilePath) @@ -71,7 +92,9 @@ func resourceArmDataLakeStoreFileCreate(d *schema.ResourceData, meta interface{} return err } - _, err = client.Create(ctx, accountName, remoteFilePath, ioutil.NopCloser(bytes.NewReader(fileContents)), utils.Bool(false), filesystem.CLOSE, nil, nil) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + _, err = client.Create(waitCtx, accountName, remoteFilePath, ioutil.NopCloser(bytes.NewReader(fileContents)), utils.Bool(false), filesystem.CLOSE, nil, nil) if err != nil { return fmt.Errorf("Error issuing create request for Data Lake Store File %q : %+v", remoteFilePath, err) } @@ -117,7 +140,9 @@ func resourceArmDataLakeStoreFileDelete(d *schema.ResourceData, meta interface{} return err } - resp, err := client.Delete(ctx, id.storageAccountName, id.filePath, utils.Bool(false)) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, id.storageAccountName, id.filePath, utils.Bool(false)) if err != nil { if !response.WasNotFound(resp.Response.Response) { return fmt.Errorf("Error issuing delete request for Data Lake Store File %q (Account %q): %+v", id.filePath, id.storageAccountName, err) diff --git a/azurerm/resource_arm_data_lake_store_file_test.go b/azurerm/resource_arm_data_lake_store_file_test.go index 7dbd430a8944..b4b6eb8e18ca 100644 --- a/azurerm/resource_arm_data_lake_store_file_test.go +++ b/azurerm/resource_arm_data_lake_store_file_test.go @@ -38,6 +38,32 @@ func TestAccAzureRMDataLakeStoreFile_basic(t *testing.T) { }) } +func TestAccAzureRMDataLakeStoreFile_requiresImport(t *testing.T) { + resourceName := "azurerm_data_lake_store_file.test" + + ri := acctest.RandInt() + rs := acctest.RandString(4) + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDataLakeStoreFileDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDataLakeStoreFile_basic(ri, rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDataLakeStoreFileExists(resourceName), + ), + }, + { + Config: testAccAzureRMDataLakeStoreFile_requiresImport(ri, rs, location), + ExpectError: testRequiresImportError("azurerm_data_lake_store_file"), + }, + }, + }) +} + func testCheckAzureRMDataLakeStoreFileExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { // Ensure we have enough information in state to look up in API @@ -113,3 +139,16 @@ resource "azurerm_data_lake_store_file" "test" { } `, rInt, location, rs, location) } + +func testAccAzureRMDataLakeStoreFile_requiresImport(rInt int, rs, location string) string { + template := testAccAzureRMDataLakeStoreFile_basic(rInt, rs, location) + return fmt.Sprintf(` +%s + +resource "azurerm_data_lake_store_file" "import" { + remote_file_path = "/test/application_gateway_test.cer" + account_name = "${azurerm_data_lake_store.test.name}" + local_file_path = "./testdata/application_gateway_test.cer" +} +`, template) +} diff --git a/azurerm/resource_arm_data_lake_store_firewall_rule.go b/azurerm/resource_arm_data_lake_store_firewall_rule.go index 4f8993ffa9ef..6145ce29f9cb 100644 --- a/azurerm/resource_arm_data_lake_store_firewall_rule.go +++ b/azurerm/resource_arm_data_lake_store_firewall_rule.go @@ -1,17 +1,21 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/datalake/store/mgmt/2016-11-01/account" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "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/utils" ) +// TODO: rename this resource to `azurerm_data_lake_store_account_firewall_rule` func resourceArmDataLakeStoreFirewallRule() *schema.Resource { return &schema.Resource{ Create: resourceArmDateLakeStoreAccountFirewallRuleCreateUpdate, @@ -21,6 +25,11 @@ func resourceArmDataLakeStoreFirewallRule() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 5), + Update: schema.DefaultTimeout(time.Minute * 5), + Delete: schema.DefaultTimeout(time.Minute * 5), + }, Schema: map[string]*schema.Schema{ "name": { @@ -61,10 +70,25 @@ func resourceArmDateLakeStoreAccountFirewallRuleCreateUpdate(d *schema.ResourceD name := d.Get("name").(string) accountName := d.Get("account_name").(string) resourceGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, accountName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Data Lake Store Firewall Rule %q (Account %q / Resource Group %q): %+v", name, accountName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_data_lake_store_firewall_rule", *resp.ID) + } + } + startIPAddress := d.Get("start_ip_address").(string) endIPAddress := d.Get("end_ip_address").(string) - log.Printf("[INFO] preparing arguments for Date Lake Store Firewall Rule creation %q (Resource Group %q)", name, resourceGroup) + log.Printf("[INFO] preparing arguments for Date Lake Store Firewall Rule creation %q (Resource Group %q)", name, resourceGroup) dateLakeStore := account.CreateOrUpdateFirewallRuleParameters{ CreateOrUpdateFirewallRuleProperties: &account.CreateOrUpdateFirewallRuleProperties{ @@ -73,7 +97,9 @@ func resourceArmDateLakeStoreAccountFirewallRuleCreateUpdate(d *schema.ResourceD }, } - _, err := client.CreateOrUpdate(ctx, resourceGroup, accountName, name, dateLakeStore) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, resourceGroup, accountName, name, dateLakeStore) if err != nil { return fmt.Errorf("Error issuing create request for Data Lake Store %q (Resource Group %q): %+v", name, resourceGroup, err) } @@ -138,7 +164,9 @@ func resourceArmDateLakeStoreAccountFirewallRuleDelete(d *schema.ResourceData, m accountName := id.Path["accounts"] name := id.Path["firewallRules"] - resp, err := client.Delete(ctx, resourceGroup, accountName, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resourceGroup, accountName, name) if err != nil { if response.WasNotFound(resp.Response) { return nil diff --git a/azurerm/resource_arm_data_lake_store_firewall_rule_test.go b/azurerm/resource_arm_data_lake_store_firewall_rule_test.go index 54a5976d7f9d..880c29a3896b 100644 --- a/azurerm/resource_arm_data_lake_store_firewall_rule_test.go +++ b/azurerm/resource_arm_data_lake_store_firewall_rule_test.go @@ -5,10 +5,11 @@ import ( "net/http" "testing" + "strconv" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" - "strconv" ) func TestAccAzureRMDataLakeStoreFirewallRule_basic(t *testing.T) { @@ -39,6 +40,34 @@ func TestAccAzureRMDataLakeStoreFirewallRule_basic(t *testing.T) { }) } +func TestAccAzureRMDataLakeStoreFirewallRule_requiresImport(t *testing.T) { + resourceName := "azurerm_data_lake_store_firewall_rule.test" + ri := acctest.RandInt() + location := testLocation() + startIP := "1.1.1.1" + endIP := "2.2.2.2" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDataLakeStoreFirewallRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDataLakeStoreFirewallRule_basic(ri, location, startIP, endIP), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDataLakeStoreFirewallRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "start_ip_address", startIP), + resource.TestCheckResourceAttr(resourceName, "end_ip_address", endIP), + ), + }, + { + Config: testAccAzureRMDataLakeStoreFirewallRule_requiresImport(ri, location, startIP, endIP), + ExpectError: testRequiresImportError("azurerm_data_lake_store_firewall_rule"), + }, + }, + }) +} + func TestAccAzureRMDataLakeStoreFirewallRule_update(t *testing.T) { resourceName := "azurerm_data_lake_store_firewall_rule.test" ri := acctest.RandInt() @@ -155,6 +184,7 @@ func testCheckAzureRMDataLakeStoreFirewallRuleDestroy(s *terraform.State) error } func testAccAzureRMDataLakeStoreFirewallRule_basic(rInt int, location, startIP, endIP string) string { + rs := strconv.Itoa(rInt)[0:15] return fmt.Sprintf(` resource "azurerm_resource_group" "test" { name = "acctestRG-%d" @@ -174,5 +204,20 @@ resource "azurerm_data_lake_store_firewall_rule" "test" { start_ip_address = "%s" end_ip_address = "%s" } -`, rInt, location, strconv.Itoa(rInt)[0:15], startIP, endIP) +`, rInt, location, rs, startIP, endIP) +} + +func testAccAzureRMDataLakeStoreFirewallRule_requiresImport(rInt int, location, startIP, endIP string) string { + template := testAccAzureRMDataLakeStoreFirewallRule_basic(rInt, location, startIP, endIP) + return fmt.Sprintf(` +%s + +resource "azurerm_data_lake_store_firewall_rule" "import" { + name = "${azurerm_data_lake_store_firewall_rule.test.name}" + account_name = "${azurerm_data_lake_store_firewall_rule.test.account_name}" + resource_group_name = "${azurerm_data_lake_store_firewall_rule.test.resource_group_name}" + start_ip_address = "${azurerm_data_lake_store_firewall_rule.test.start_ip_address}" + end_ip_address = "${azurerm_data_lake_store_firewall_rule.test.end_ip_address}" +} +`, template) } diff --git a/azurerm/resource_arm_data_lake_store_test.go b/azurerm/resource_arm_data_lake_store_test.go index 0304cd7c4b92..56f16412d9e3 100644 --- a/azurerm/resource_arm_data_lake_store_test.go +++ b/azurerm/resource_arm_data_lake_store_test.go @@ -38,6 +38,30 @@ func TestAccAzureRMDataLakeStore_basic(t *testing.T) { }) } +func TestAccAzureRMDataLakeStore_requiresImport(t *testing.T) { + resourceName := "azurerm_data_lake_store.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDataLakeStoreDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDataLakeStore_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDataLakeStoreExists(resourceName), + ), + }, + { + Config: testAccAzureRMDataLakeStore_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_data_lake_store"), + }, + }, + }) +} + func TestAccAzureRMDataLakeStore_tier(t *testing.T) { resourceName := "azurerm_data_lake_store.test" ri := acctest.RandInt() @@ -225,6 +249,7 @@ func testCheckAzureRMDataLakeStoreDestroy(s *terraform.State) error { } func testAccAzureRMDataLakeStore_basic(rInt int, location string) string { + rs := strconv.Itoa(rInt)[0:15] return fmt.Sprintf(` resource "azurerm_resource_group" "test" { name = "acctestRG-%d" @@ -236,7 +261,20 @@ resource "azurerm_data_lake_store" "test" { resource_group_name = "${azurerm_resource_group.test.name}" location = "${azurerm_resource_group.test.location}" } -`, rInt, location, strconv.Itoa(rInt)[0:15]) +`, rInt, location, rs) +} + +func testAccAzureRMDataLakeStore_requiresImport(rInt int, location string) string { + template := testAccAzureRMDataLakeStore_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_data_lake_store" "import" { + name = "${azurerm_data_lake_store.test.name}" + resource_group_name = "${azurerm_data_lake_store.test.resource_group_name}" + location = "${azurerm_data_lake_store.test.location}" +} +`, template) } func testAccAzureRMDataLakeStore_tier(rInt int, location string) string { diff --git a/azurerm/resource_arm_dns_a_record.go b/azurerm/resource_arm_dns_a_record.go index c8459d777aae..d117da0f2dc6 100644 --- a/azurerm/resource_arm_dns_a_record.go +++ b/azurerm/resource_arm_dns_a_record.go @@ -1,11 +1,14 @@ package azurerm import ( + "context" "fmt" "net/http" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/dns/mgmt/2018-03-01-preview/dns" "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" ) @@ -18,6 +21,11 @@ func resourceArmDnsARecord() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 10), + Update: schema.DefaultTimeout(time.Minute * 10), + Delete: schema.DefaultTimeout(time.Minute * 10), + }, Schema: map[string]*schema.Schema{ "name": { @@ -51,12 +59,27 @@ func resourceArmDnsARecord() *schema.Resource { } func resourceArmDnsARecordCreateOrUpdate(d *schema.ResourceData, meta interface{}) error { - dnsClient := meta.(*ArmClient).dnsClient + client := meta.(*ArmClient).dnsClient ctx := meta.(*ArmClient).StopContext name := d.Get("name").(string) - resGroup := d.Get("resource_group_name").(string) zoneName := d.Get("zone_name").(string) + resGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, zoneName, name, dns.A) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of DNS A Record %q (Zone %q / Resource Group %q): %+v", name, zoneName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_dns_a_record", *resp.ID) + } + } + ttl := int64(d.Get("ttl").(int)) tags := d.Get("tags").(map[string]interface{}) @@ -76,7 +99,9 @@ func resourceArmDnsARecordCreateOrUpdate(d *schema.ResourceData, meta interface{ eTag := "" ifNoneMatch := "" // set to empty to allow updates to records after creation - resp, err := dnsClient.CreateOrUpdate(ctx, resGroup, zoneName, name, dns.A, parameters, eTag, ifNoneMatch) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + resp, err := client.CreateOrUpdate(waitCtx, resGroup, zoneName, name, dns.A, parameters, eTag, ifNoneMatch) if err != nil { return err } @@ -138,9 +163,11 @@ func resourceArmDnsARecordDelete(d *schema.ResourceData, meta interface{}) error name := id.Path["A"] zoneName := id.Path["dnszones"] - resp, error := dnsClient.Delete(ctx, resGroup, zoneName, name, dns.A, "") + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := dnsClient.Delete(waitCtx, resGroup, zoneName, name, dns.A, "") if resp.StatusCode != http.StatusOK { - return fmt.Errorf("Error deleting DNS A Record %s: %+v", name, error) + return fmt.Errorf("Error deleting DNS A Record %s: %+v", name, err) } return nil diff --git a/azurerm/resource_arm_dns_a_record_test.go b/azurerm/resource_arm_dns_a_record_test.go index 022f44f607e5..8c9f7011cc5f 100644 --- a/azurerm/resource_arm_dns_a_record_test.go +++ b/azurerm/resource_arm_dns_a_record_test.go @@ -31,6 +31,30 @@ func TestAccAzureRMDnsARecord_basic(t *testing.T) { }) } +func TestAccAzureRMDnsARecord_requiresImport(t *testing.T) { + resourceName := "azurerm_dns_a_record.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDnsARecordDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDnsARecord_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDnsARecordExists(resourceName), + ), + }, + { + Config: testAccAzureRMDnsARecord_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_dns_a_record"), + }, + }, + }) +} + func TestAccAzureRMDnsARecord_updateRecords(t *testing.T) { resourceName := "azurerm_dns_a_record.test" ri := acctest.RandInt() @@ -172,6 +196,21 @@ resource "azurerm_dns_a_record" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMDnsARecord_requiresImport(rInt int, location string) string { + template := testAccAzureRMDnsARecord_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_dns_a_record" "import" { + name = "${azurerm_dns_a_record.test.name}" + resource_group_name = "${azurerm_dns_a_record.test.resource_group_name}" + zone_name = "${azurerm_dns_a_record.test.zone_name}" + ttl = 300 + records = ["1.2.3.4", "1.2.4.5"] +} +`, template) +} + func testAccAzureRMDnsARecord_updateRecords(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_dns_aaaa_record.go b/azurerm/resource_arm_dns_aaaa_record.go index 68b05687d087..92b64b89072c 100644 --- a/azurerm/resource_arm_dns_aaaa_record.go +++ b/azurerm/resource_arm_dns_aaaa_record.go @@ -1,11 +1,14 @@ package azurerm import ( + "context" "fmt" "net/http" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/dns/mgmt/2018-03-01-preview/dns" "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" ) @@ -18,6 +21,11 @@ func resourceArmDnsAAAARecord() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 10), + Update: schema.DefaultTimeout(time.Minute * 10), + Delete: schema.DefaultTimeout(time.Minute * 10), + }, Schema: map[string]*schema.Schema{ "name": { @@ -57,6 +65,21 @@ func resourceArmDnsAaaaRecordCreateOrUpdate(d *schema.ResourceData, meta interfa name := d.Get("name").(string) resGroup := d.Get("resource_group_name").(string) zoneName := d.Get("zone_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, zoneName, name, dns.AAAA) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of DNS AAAA Record %q (Zone %q / Resource Group %q): %+v", name, zoneName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_dns_aaaa_record", *resp.ID) + } + } + ttl := int64(d.Get("ttl").(int)) tags := d.Get("tags").(map[string]interface{}) @@ -76,7 +99,9 @@ func resourceArmDnsAaaaRecordCreateOrUpdate(d *schema.ResourceData, meta interfa eTag := "" ifNoneMatch := "" // set to empty to allow updates to records after creation - resp, err := client.CreateOrUpdate(ctx, resGroup, zoneName, name, dns.AAAA, parameters, eTag, ifNoneMatch) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + resp, err := client.CreateOrUpdate(waitCtx, resGroup, zoneName, name, dns.AAAA, parameters, eTag, ifNoneMatch) if err != nil { return err } @@ -138,7 +163,9 @@ func resourceArmDnsAaaaRecordDelete(d *schema.ResourceData, meta interface{}) er name := id.Path["AAAA"] zoneName := id.Path["dnszones"] - resp, error := dnsClient.Delete(ctx, resGroup, zoneName, name, dns.AAAA, "") + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, error := dnsClient.Delete(waitCtx, resGroup, zoneName, name, dns.AAAA, "") if resp.StatusCode != http.StatusOK { return fmt.Errorf("Error deleting DNS AAAA Record %s: %+v", name, error) } @@ -147,11 +174,13 @@ func resourceArmDnsAaaaRecordDelete(d *schema.ResourceData, meta interface{}) er } func flattenAzureRmDnsAaaaRecords(records *[]dns.AaaaRecord) []string { - results := make([]string, 0, len(*records)) + results := make([]string, 0) if records != nil { for _, record := range *records { - results = append(results, *record.Ipv6Address) + if record.Ipv6Address != nil { + results = append(results, *record.Ipv6Address) + } } } @@ -160,13 +189,14 @@ func flattenAzureRmDnsAaaaRecords(records *[]dns.AaaaRecord) []string { func expandAzureRmDnsAaaaRecords(d *schema.ResourceData) ([]dns.AaaaRecord, error) { recordStrings := d.Get("records").(*schema.Set).List() - records := make([]dns.AaaaRecord, len(recordStrings)) + records := make([]dns.AaaaRecord, 0) - for i, v := range recordStrings { + for _, v := range recordStrings { ipv6 := v.(string) - records[i] = dns.AaaaRecord{ + record := dns.AaaaRecord{ Ipv6Address: &ipv6, } + records = append(records, record) } return records, nil diff --git a/azurerm/resource_arm_dns_aaaa_record_test.go b/azurerm/resource_arm_dns_aaaa_record_test.go index a4df807e169e..7222a33db42a 100644 --- a/azurerm/resource_arm_dns_aaaa_record_test.go +++ b/azurerm/resource_arm_dns_aaaa_record_test.go @@ -31,6 +31,30 @@ func TestAccAzureRMDnsAAAARecord_basic(t *testing.T) { }) } +func TestAccAzureRMDnsAAAARecord_requiresImport(t *testing.T) { + resourceName := "azurerm_dns_aaaa_record.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDnsAaaaRecordDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDnsAAAARecord_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDnsAaaaRecordExists(resourceName), + ), + }, + { + Config: testAccAzureRMDnsAAAARecord_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_dns_aaaa_record"), + }, + }, + }) +} + func TestAccAzureRMDnsAAAARecord_updateRecords(t *testing.T) { resourceName := "azurerm_dns_aaaa_record.test" ri := acctest.RandInt() @@ -172,6 +196,21 @@ resource "azurerm_dns_aaaa_record" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMDnsAAAARecord_requiresImport(rInt int, location string) string { + template := testAccAzureRMDnsAAAARecord_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_dns_aaaa_record" "import" { + name = "${azurerm_dns_aaaa_record.test.name}" + resource_group_name = "${azurerm_dns_aaaa_record.test.resource_group_name}" + zone_name = "${azurerm_dns_aaaa_record.test.zone_name}" + ttl = 300 + records = ["2607:f8b0:4009:1803::1005", "2607:f8b0:4009:1803::1006"] +} +`, template) +} + func testAccAzureRMDnsAAAARecord_updateRecords(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_dns_caa_record.go b/azurerm/resource_arm_dns_caa_record.go index afa9053da99d..c82c3c1b5e5a 100644 --- a/azurerm/resource_arm_dns_caa_record.go +++ b/azurerm/resource_arm_dns_caa_record.go @@ -2,13 +2,16 @@ package azurerm import ( "bytes" + "context" "fmt" "net/http" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/dns/mgmt/2018-03-01-preview/dns" "github.com/hashicorp/terraform/helper/hashcode" "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" ) @@ -21,6 +24,11 @@ func resourceArmDnsCaaRecord() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 10), + Update: schema.DefaultTimeout(time.Minute * 10), + Delete: schema.DefaultTimeout(time.Minute * 10), + }, Schema: map[string]*schema.Schema{ "name": { @@ -83,6 +91,21 @@ func resourceArmDnsCaaRecordCreateOrUpdate(d *schema.ResourceData, meta interfac name := d.Get("name").(string) resGroup := d.Get("resource_group_name").(string) zoneName := d.Get("zone_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, zoneName, name, dns.CAA) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of DNS CAA Record %q (Zone %q / Resource Group %q): %+v", name, zoneName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_dns_caa_record", *resp.ID) + } + } + ttl := int64(d.Get("ttl").(int)) tags := d.Get("tags").(map[string]interface{}) @@ -102,7 +125,9 @@ func resourceArmDnsCaaRecordCreateOrUpdate(d *schema.ResourceData, meta interfac eTag := "" ifNoneMatch := "" // set to empty to allow updates to records after creation - resp, err := client.CreateOrUpdate(ctx, resGroup, zoneName, name, dns.CAA, parameters, eTag, ifNoneMatch) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + resp, err := client.CreateOrUpdate(waitCtx, resGroup, zoneName, name, dns.CAA, parameters, eTag, ifNoneMatch) if err != nil { return err } @@ -164,7 +189,9 @@ func resourceArmDnsCaaRecordDelete(d *schema.ResourceData, meta interface{}) err name := id.Path["CAA"] zoneName := id.Path["dnszones"] - resp, error := client.Delete(ctx, resGroup, zoneName, name, dns.CAA, "") + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, error := client.Delete(waitCtx, resGroup, zoneName, name, dns.CAA, "") if resp.StatusCode != http.StatusOK { return fmt.Errorf("Error deleting DNS CAA Record %s: %+v", name, error) } @@ -173,7 +200,7 @@ func resourceArmDnsCaaRecordDelete(d *schema.ResourceData, meta interface{}) err } func flattenAzureRmDnsCaaRecords(records *[]dns.CaaRecord) []map[string]interface{} { - results := make([]map[string]interface{}, 0, len(*records)) + results := make([]map[string]interface{}, 0) if records != nil { for _, record := range *records { @@ -190,9 +217,9 @@ func flattenAzureRmDnsCaaRecords(records *[]dns.CaaRecord) []map[string]interfac func expandAzureRmDnsCaaRecords(d *schema.ResourceData) ([]dns.CaaRecord, error) { recordStrings := d.Get("record").(*schema.Set).List() - records := make([]dns.CaaRecord, len(recordStrings)) + records := make([]dns.CaaRecord, 0) - for i, v := range recordStrings { + for _, v := range recordStrings { record := v.(map[string]interface{}) flags := int32(record["flags"].(int)) tag := record["tag"].(string) @@ -204,7 +231,7 @@ func expandAzureRmDnsCaaRecords(d *schema.ResourceData) ([]dns.CaaRecord, error) Value: &value, } - records[i] = caaRecord + records = append(records, caaRecord) } return records, nil diff --git a/azurerm/resource_arm_dns_caa_record_test.go b/azurerm/resource_arm_dns_caa_record_test.go index ebbdd0713490..d01d42e519cc 100644 --- a/azurerm/resource_arm_dns_caa_record_test.go +++ b/azurerm/resource_arm_dns_caa_record_test.go @@ -14,7 +14,8 @@ import ( func TestAccAzureRMDnsCaaRecord_basic(t *testing.T) { resourceName := "azurerm_dns_caa_record.test" ri := acctest.RandInt() - config := testAccAzureRMDnsCaaRecord_basic(ri, testLocation()) + location := testLocation() + config := testAccAzureRMDnsCaaRecord_basic(ri, location) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -31,6 +32,30 @@ func TestAccAzureRMDnsCaaRecord_basic(t *testing.T) { }) } +func TestAccAzureRMDnsCaaRecord_requiresImport(t *testing.T) { + resourceName := "azurerm_dns_caa_record.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDnsCaaRecordDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDnsCaaRecord_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDnsCaaRecordExists(resourceName), + ), + }, + { + Config: testAccAzureRMDnsCaaRecord_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_dns_caa_record"), + }, + }, + }) +} + func TestAccAzureRMDnsCaaRecord_updateRecords(t *testing.T) { resourceName := "azurerm_dns_caa_record.test" ri := acctest.RandInt() @@ -172,7 +197,7 @@ resource "azurerm_dns_caa_record" "test" { flags = 0 tag = "issue" value = "example.com" - } + } record { flags = 0 @@ -184,17 +209,55 @@ resource "azurerm_dns_caa_record" "test" { flags = 1 tag = "issuewild" value = ";" - } + } record { flags = 0 tag = "iodef" value = "mailto:terraform@nonexist.tld" - } + } } `, rInt, location, rInt, rInt) } +func testAccAzureRMDnsCaaRecord_requiresImport(rInt int, location string) string { + template := testAccAzureRMDnsCaaRecord_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_dns_caa_record" "import" { + name = "${azurerm_dns_caa_record.test.name}" + resource_group_name = "${azurerm_dns_caa_record.test.resource_group_name}" + zone_name = "${azurerm_dns_caa_record.test.zone_name}" + ttl = 300 + + record { + flags = 0 + tag = "issue" + value = "example.com" + } + + record { + flags = 0 + tag = "issue" + value = "example.net" + } + + record { + flags = 1 + tag = "issuewild" + value = ";" + } + + record { + flags = 0 + tag = "iodef" + value = "mailto:terraform@nonexist.tld" + } +} +`, template) +} + func testAccAzureRMDnsCaaRecord_updateRecords(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_dns_cname_record.go b/azurerm/resource_arm_dns_cname_record.go index f99b524329c5..121244d40ee1 100644 --- a/azurerm/resource_arm_dns_cname_record.go +++ b/azurerm/resource_arm_dns_cname_record.go @@ -1,11 +1,14 @@ package azurerm import ( + "context" "fmt" "net/http" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/dns/mgmt/2018-03-01-preview/dns" "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" ) @@ -18,6 +21,11 @@ func resourceArmDnsCNameRecord() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 10), + Update: schema.DefaultTimeout(time.Minute * 10), + Delete: schema.DefaultTimeout(time.Minute * 10), + }, Schema: map[string]*schema.Schema{ "name": { @@ -33,14 +41,6 @@ func resourceArmDnsCNameRecord() *schema.Resource { Required: true, }, - "records": { - Type: schema.TypeString, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - Removed: "Use `record` instead. This attribute will be removed in a future version", - }, - "record": { Type: schema.TypeString, Required: true, @@ -57,12 +57,27 @@ func resourceArmDnsCNameRecord() *schema.Resource { } func resourceArmDnsCNameRecordCreateOrUpdate(d *schema.ResourceData, meta interface{}) error { - dnsClient := meta.(*ArmClient).dnsClient + client := meta.(*ArmClient).dnsClient ctx := meta.(*ArmClient).StopContext name := d.Get("name").(string) resGroup := d.Get("resource_group_name").(string) zoneName := d.Get("zone_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, zoneName, name, dns.CNAME) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of DNS CAME Record %q (Zone %q / Resource Group %q): %+v", name, zoneName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_dns_cname_record", *resp.ID) + } + } + ttl := int64(d.Get("ttl").(int)) record := d.Get("record").(string) tags := d.Get("tags").(map[string]interface{}) @@ -80,7 +95,9 @@ func resourceArmDnsCNameRecordCreateOrUpdate(d *schema.ResourceData, meta interf eTag := "" ifNoneMatch := "" // set to empty to allow updates to records after creation - resp, err := dnsClient.CreateOrUpdate(ctx, resGroup, zoneName, name, dns.CNAME, parameters, eTag, ifNoneMatch) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + resp, err := client.CreateOrUpdate(waitCtx, resGroup, zoneName, name, dns.CNAME, parameters, eTag, ifNoneMatch) if err != nil { return err } @@ -145,7 +162,9 @@ func resourceArmDnsCNameRecordDelete(d *schema.ResourceData, meta interface{}) e name := id.Path["CNAME"] zoneName := id.Path["dnszones"] - resp, error := dnsClient.Delete(ctx, resGroup, zoneName, name, dns.CNAME, "") + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, error := dnsClient.Delete(waitCtx, resGroup, zoneName, name, dns.CNAME, "") if resp.StatusCode != http.StatusOK { return fmt.Errorf("Error deleting DNS CNAME Record %s: %+v", name, error) } diff --git a/azurerm/resource_arm_dns_cname_record_test.go b/azurerm/resource_arm_dns_cname_record_test.go index f5bfa4f68079..5ee7bfb76f92 100644 --- a/azurerm/resource_arm_dns_cname_record_test.go +++ b/azurerm/resource_arm_dns_cname_record_test.go @@ -31,6 +31,30 @@ func TestAccAzureRMDnsCNameRecord_basic(t *testing.T) { }) } +func TestAccAzureRMDnsCNameRecord_requiresImport(t *testing.T) { + resourceName := "azurerm_dns_cname_record.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDnsCNameRecordDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDnsCNameRecord_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDnsCNameRecordExists(resourceName), + ), + }, + { + Config: testAccAzureRMDnsCNameRecord_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_dns_cname_record"), + }, + }, + }) +} + func TestAccAzureRMDnsCNameRecord_subdomain(t *testing.T) { resourceName := "azurerm_dns_cname_record.test" ri := acctest.RandInt() @@ -191,6 +215,21 @@ resource "azurerm_dns_cname_record" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMDnsCNameRecord_requiresImport(rInt int, location string) string { + template := testAccAzureRMDnsCNameRecord_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_dns_cname_record" "import" { + name = "${azurerm_dns_cname_record.test.name}" + resource_group_name = "${azurerm_dns_cname_record.test.resource_group_name}" + zone_name = "${azurerm_dns_cname_record.test.zone_name}" + ttl = 300 + record = "contoso.com" +} +`, template) +} + func testAccAzureRMDnsCNameRecord_subdomain(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_dns_mx_record.go b/azurerm/resource_arm_dns_mx_record.go index e8d9593c6aa6..7f9e0de4ab4c 100644 --- a/azurerm/resource_arm_dns_mx_record.go +++ b/azurerm/resource_arm_dns_mx_record.go @@ -2,13 +2,16 @@ package azurerm import ( "bytes" + "context" "fmt" "net/http" "strconv" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/dns/mgmt/2018-03-01-preview/dns" "github.com/hashicorp/terraform/helper/hashcode" "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" ) @@ -21,6 +24,11 @@ func resourceArmDnsMxRecord() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 10), + Update: schema.DefaultTimeout(time.Minute * 10), + Delete: schema.DefaultTimeout(time.Minute * 10), + }, Schema: map[string]*schema.Schema{ "name": { @@ -73,6 +81,21 @@ func resourceArmDnsMxRecordCreateOrUpdate(d *schema.ResourceData, meta interface name := d.Get("name").(string) resGroup := d.Get("resource_group_name").(string) zoneName := d.Get("zone_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, zoneName, name, dns.MX) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of DNS MX Record %q (Zone %q / Resource Group %q): %+v", name, zoneName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_dns_mx_record", *resp.ID) + } + } + ttl := int64(d.Get("ttl").(int)) tags := d.Get("tags").(map[string]interface{}) records, err := expandAzureRmDnsMxRecords(d) @@ -91,7 +114,9 @@ func resourceArmDnsMxRecordCreateOrUpdate(d *schema.ResourceData, meta interface eTag := "" ifNoneMatch := "" // set to empty to allow updates to records after creation - resp, err := client.CreateOrUpdate(ctx, resGroup, zoneName, name, dns.MX, parameters, eTag, ifNoneMatch) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + resp, err := client.CreateOrUpdate(waitCtx, resGroup, zoneName, name, dns.MX, parameters, eTag, ifNoneMatch) if err != nil { return err } @@ -153,7 +178,9 @@ func resourceArmDnsMxRecordDelete(d *schema.ResourceData, meta interface{}) erro name := id.Path["MX"] zoneName := id.Path["dnszones"] - resp, error := client.Delete(ctx, resGroup, zoneName, name, dns.MX, "") + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, error := client.Delete(waitCtx, resGroup, zoneName, name, dns.MX, "") if resp.StatusCode != http.StatusOK { return fmt.Errorf("Error deleting DNS MX Record %s: %+v", name, error) } @@ -165,7 +192,7 @@ func resourceArmDnsMxRecordDelete(d *schema.ResourceData, meta interface{}) erro // the expectations of the ResourceData schema, so that this data can be // managed by Terradata state. func flattenAzureRmDnsMxRecords(records *[]dns.MxRecord) []map[string]interface{} { - results := make([]map[string]interface{}, 0, len(*records)) + results := make([]map[string]interface{}, 0) if records != nil { for _, record := range *records { diff --git a/azurerm/resource_arm_dns_mx_record_test.go b/azurerm/resource_arm_dns_mx_record_test.go index c7972c3fc07b..a5c7f53d3ece 100644 --- a/azurerm/resource_arm_dns_mx_record_test.go +++ b/azurerm/resource_arm_dns_mx_record_test.go @@ -31,6 +31,30 @@ func TestAccAzureRMDnsMxRecord_basic(t *testing.T) { }) } +func TestAccAzureRMDnsMxRecord_requiresImport(t *testing.T) { + resourceName := "azurerm_dns_mx_record.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDnsMxRecordDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDnsMxRecord_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDnsMxRecordExists(resourceName), + ), + }, + { + Config: testAccAzureRMDnsMxRecord_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_dns_mx_record"), + }, + }, + }) +} + func TestAccAzureRMDnsMxRecord_updateRecords(t *testing.T) { resourceName := "azurerm_dns_mx_record.test" ri := acctest.RandInt() @@ -181,6 +205,30 @@ resource "azurerm_dns_mx_record" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMDnsMxRecord_requiresImport(rInt int, location string) string { + template := testAccAzureRMDnsMxRecord_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_dns_mx_record" "import" { + name = "${azurerm_dns_mx_record.test.name}" + resource_group_name = "${azurerm_dns_mx_record.test.resource_group_name}" + zone_name = "${azurerm_dns_mx_record.test.zone_name}" + ttl = 300 + + record { + preference = "10" + exchange = "mail1.contoso.com" + } + + record { + preference = "20" + exchange = "mail2.contoso.com" + } +} +`, template) +} + func testAccAzureRMDnsMxRecord_updateRecords(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_dns_ns_record.go b/azurerm/resource_arm_dns_ns_record.go index 4319c5357b21..719b4ada68e2 100644 --- a/azurerm/resource_arm_dns_ns_record.go +++ b/azurerm/resource_arm_dns_ns_record.go @@ -1,11 +1,14 @@ package azurerm import ( + "context" "fmt" "net/http" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/dns/mgmt/2018-03-01-preview/dns" "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" ) @@ -18,6 +21,11 @@ func resourceArmDnsNsRecord() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 10), + Update: schema.DefaultTimeout(time.Minute * 10), + Delete: schema.DefaultTimeout(time.Minute * 10), + }, Schema: map[string]*schema.Schema{ "name": { @@ -69,12 +77,27 @@ func resourceArmDnsNsRecord() *schema.Resource { } func resourceArmDnsNsRecordCreateOrUpdate(d *schema.ResourceData, meta interface{}) error { - dnsClient := meta.(*ArmClient).dnsClient + client := meta.(*ArmClient).dnsClient ctx := meta.(*ArmClient).StopContext name := d.Get("name").(string) resGroup := d.Get("resource_group_name").(string) zoneName := d.Get("zone_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, zoneName, name, dns.NS) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of DNS NS Record %q (Zone %q / Resource Group %q): %+v", name, zoneName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_dns_ns_record", *resp.ID) + } + } + ttl := int64(d.Get("ttl").(int)) tags := d.Get("tags").(map[string]interface{}) records, err := expandAzureRmDnsNsRecords(d) @@ -93,7 +116,9 @@ func resourceArmDnsNsRecordCreateOrUpdate(d *schema.ResourceData, meta interface eTag := "" ifNoneMatch := "" // set to empty to allow updates to records after creation - resp, err := dnsClient.CreateOrUpdate(ctx, resGroup, zoneName, name, dns.NS, parameters, eTag, ifNoneMatch) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + resp, err := client.CreateOrUpdate(waitCtx, resGroup, zoneName, name, dns.NS, parameters, eTag, ifNoneMatch) if err != nil { return err } @@ -161,7 +186,9 @@ func resourceArmDnsNsRecordDelete(d *schema.ResourceData, meta interface{}) erro name := id.Path["NS"] zoneName := id.Path["dnszones"] - resp, error := dnsClient.Delete(ctx, resGroup, zoneName, name, dns.NS, "") + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, error := dnsClient.Delete(waitCtx, resGroup, zoneName, name, dns.NS, "") if resp.StatusCode != http.StatusOK { return fmt.Errorf("Error deleting DNS NS Record %s: %+v", name, error) } diff --git a/azurerm/resource_arm_dns_ns_record_test.go b/azurerm/resource_arm_dns_ns_record_test.go index 3e2b2ddd1ec7..66d6b1340f63 100644 --- a/azurerm/resource_arm_dns_ns_record_test.go +++ b/azurerm/resource_arm_dns_ns_record_test.go @@ -52,6 +52,30 @@ func TestAccAzureRMDnsNsRecord_basic(t *testing.T) { }) } +func TestAccAzureRMDnsNsRecord_requiresImport(t *testing.T) { + resourceName := "azurerm_dns_ns_record.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDnsNsRecordDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDnsNsRecord_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDnsNsRecordExists(resourceName), + ), + }, + { + Config: testAccAzureRMDnsNsRecord_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_dns_ns_record"), + }, + }, + }) +} + //TODO: remove this once we remove the `record` attribute func TestAccAzureRMDnsNsRecord_deprecatedUpdateRecords(t *testing.T) { resourceName := "azurerm_dns_ns_record.test" @@ -286,6 +310,22 @@ resource "azurerm_dns_ns_record" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMDnsNsRecord_requiresImport(rInt int, location string) string { + template := testAccAzureRMDnsNsRecord_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_dns_ns_record" "import" { + name = "${azurerm_dns_ns_record.test.name}" + resource_group_name = "${azurerm_dns_ns_record.test.resource_group_name}" + zone_name = "${azurerm_dns_ns_record.test.zone_name}" + ttl = 300 + + records = ["ns1.contoso.com", "ns2.contoso.com"] +} +`, template) +} + //TODO: remove this once we remove the `record` attribute func testAccAzureRMDnsNsRecord_deprecatedBasic(rInt int, location string) string { return fmt.Sprintf(` diff --git a/azurerm/resource_arm_dns_ptr_record.go b/azurerm/resource_arm_dns_ptr_record.go index 940daf2f5483..32a56e413475 100644 --- a/azurerm/resource_arm_dns_ptr_record.go +++ b/azurerm/resource_arm_dns_ptr_record.go @@ -1,11 +1,14 @@ package azurerm import ( + "context" "fmt" "net/http" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/dns/mgmt/2018-03-01-preview/dns" "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" ) @@ -18,6 +21,11 @@ func resourceArmDnsPtrRecord() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 10), + Update: schema.DefaultTimeout(time.Minute * 10), + Delete: schema.DefaultTimeout(time.Minute * 10), + }, Schema: map[string]*schema.Schema{ "name": { @@ -57,6 +65,21 @@ func resourceArmDnsPtrRecordCreateOrUpdate(d *schema.ResourceData, meta interfac name := d.Get("name").(string) resGroup := d.Get("resource_group_name").(string) zoneName := d.Get("zone_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, zoneName, name, dns.PTR) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of DNS PTR Record %q (Zone %q / Resource Group %q): %+v", name, zoneName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_dns_ptr_record", *resp.ID) + } + } + ttl := int64(d.Get("ttl").(int)) tags := d.Get("tags").(map[string]interface{}) @@ -75,7 +98,9 @@ func resourceArmDnsPtrRecordCreateOrUpdate(d *schema.ResourceData, meta interfac eTag := "" ifNoneMatch := "" // set to empty to allow updates to records after creation - resp, err := client.CreateOrUpdate(ctx, resGroup, zoneName, name, dns.PTR, parameters, eTag, ifNoneMatch) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + resp, err := client.CreateOrUpdate(waitCtx, resGroup, zoneName, name, dns.PTR, parameters, eTag, ifNoneMatch) if err != nil { return err } @@ -141,7 +166,9 @@ func resourceArmDnsPtrRecordDelete(d *schema.ResourceData, meta interface{}) err name := id.Path["PTR"] zoneName := id.Path["dnszones"] - resp, err := dnsClient.Delete(ctx, resGroup, zoneName, name, dns.PTR, "") + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := dnsClient.Delete(waitCtx, resGroup, zoneName, name, dns.PTR, "") if err != nil { if resp.StatusCode == http.StatusNotFound { return nil diff --git a/azurerm/resource_arm_dns_ptr_record_test.go b/azurerm/resource_arm_dns_ptr_record_test.go index 6a4d580718eb..63deed8a482c 100644 --- a/azurerm/resource_arm_dns_ptr_record_test.go +++ b/azurerm/resource_arm_dns_ptr_record_test.go @@ -30,6 +30,29 @@ func TestAccAzureRMDnsPtrRecord_basic(t *testing.T) { }) } +func TestAccAzureRMDnsPtrRecord_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDnsPtrRecordDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDnsPtrRecord_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDnsPtrRecordExists("azurerm_dns_ptr_record.test"), + ), + }, + { + Config: testAccAzureRMDnsPtrRecord_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_dns_ptr_record"), + }, + }, + }) +} + func TestAccAzureRMDnsPtrRecord_updateRecords(t *testing.T) { ri := acctest.RandInt() location := testLocation() @@ -172,6 +195,21 @@ resource "azurerm_dns_ptr_record" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMDnsPtrRecord_requiresImport(rInt int, location string) string { + template := testAccAzureRMDnsPtrRecord_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_dns_ptr_record" "import" { + name = "${azurerm_dns_ptr_record.test.name}" + resource_group_name = "${azurerm_dns_ptr_record.test.resource_group_name}" + zone_name = "${azurerm_dns_ptr_record.test.zone_name}" + ttl = 300 + records = ["hashicorp.com", "microsoft.com"] +} +`, template) +} + func testAccAzureRMDnsPtrRecord_updateRecords(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_dns_srv_record.go b/azurerm/resource_arm_dns_srv_record.go index 098d10cf1bc2..ce08b33a792a 100644 --- a/azurerm/resource_arm_dns_srv_record.go +++ b/azurerm/resource_arm_dns_srv_record.go @@ -2,12 +2,15 @@ package azurerm import ( "bytes" + "context" "fmt" "net/http" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/dns/mgmt/2018-03-01-preview/dns" "github.com/hashicorp/terraform/helper/hashcode" "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" ) @@ -20,6 +23,11 @@ func resourceArmDnsSrvRecord() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 10), + Update: schema.DefaultTimeout(time.Minute * 10), + Delete: schema.DefaultTimeout(time.Minute * 10), + }, Schema: map[string]*schema.Schema{ "name": { @@ -81,6 +89,21 @@ func resourceArmDnsSrvRecordCreateOrUpdate(d *schema.ResourceData, meta interfac name := d.Get("name").(string) resGroup := d.Get("resource_group_name").(string) zoneName := d.Get("zone_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, zoneName, name, dns.SRV) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of DNS SRV Record %q (Zone %q / Resource Group %q): %+v", name, zoneName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_dns_srv_record", *resp.ID) + } + } + ttl := int64(d.Get("ttl").(int)) tags := d.Get("tags").(map[string]interface{}) @@ -100,7 +123,9 @@ func resourceArmDnsSrvRecordCreateOrUpdate(d *schema.ResourceData, meta interfac eTag := "" ifNoneMatch := "" // set to empty to allow updates to records after creation - resp, err := client.CreateOrUpdate(ctx, resGroup, zoneName, name, dns.SRV, parameters, eTag, ifNoneMatch) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + resp, err := client.CreateOrUpdate(waitCtx, resGroup, zoneName, name, dns.SRV, parameters, eTag, ifNoneMatch) if err != nil { return err } @@ -162,7 +187,9 @@ func resourceArmDnsSrvRecordDelete(d *schema.ResourceData, meta interface{}) err name := id.Path["SRV"] zoneName := id.Path["dnszones"] - resp, error := client.Delete(ctx, resGroup, zoneName, name, dns.SRV, "") + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, error := client.Delete(waitCtx, resGroup, zoneName, name, dns.SRV, "") if resp.StatusCode != http.StatusOK { return fmt.Errorf("Error deleting DNS SRV Record %s: %+v", name, error) } diff --git a/azurerm/resource_arm_dns_srv_record_test.go b/azurerm/resource_arm_dns_srv_record_test.go index 8b50e58428a2..573c9d591d63 100644 --- a/azurerm/resource_arm_dns_srv_record_test.go +++ b/azurerm/resource_arm_dns_srv_record_test.go @@ -31,6 +31,30 @@ func TestAccAzureRMDnsSrvRecord_basic(t *testing.T) { }) } +func TestAccAzureRMDnsSrvRecord_requiresImport(t *testing.T) { + resourceName := "azurerm_dns_srv_record.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDnsSrvRecordDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDnsSrvRecord_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDnsSrvRecordExists(resourceName), + ), + }, + { + Config: testAccAzureRMDnsSrvRecord_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_dns_srv_record"), + }, + }, + }) +} + func TestAccAzureRMDnsSrvRecord_updateRecords(t *testing.T) { resourceName := "azurerm_dns_srv_record.test" ri := acctest.RandInt() @@ -185,6 +209,34 @@ resource "azurerm_dns_srv_record" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMDnsSrvRecord_requiresImport(rInt int, location string) string { + template := testAccAzureRMDnsSrvRecord_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_dns_srv_record" "import" { + name = "${azurerm_dns_srv_record.test.name}" + resource_group_name = "${azurerm_dns_srv_record.test.resource_group_name}" + zone_name = "${azurerm_dns_srv_record.test.zone_name}" + ttl = 300 + + record { + priority = 1 + weight = 5 + port = 8080 + target = "target1.contoso.com" + } + + record { + priority = 2 + weight = 25 + port = 8080 + target = "target2.contoso.com" + } +} +`, template) +} + func testAccAzureRMDnsSrvRecord_updateRecords(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_dns_txt_record.go b/azurerm/resource_arm_dns_txt_record.go index 250470a82394..ad18ec22ef9d 100644 --- a/azurerm/resource_arm_dns_txt_record.go +++ b/azurerm/resource_arm_dns_txt_record.go @@ -1,11 +1,14 @@ package azurerm import ( + "context" "fmt" "net/http" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/dns/mgmt/2018-03-01-preview/dns" "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" ) @@ -18,6 +21,11 @@ func resourceArmDnsTxtRecord() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 10), + Update: schema.DefaultTimeout(time.Minute * 10), + Delete: schema.DefaultTimeout(time.Minute * 10), + }, Schema: map[string]*schema.Schema{ "name": { @@ -63,6 +71,21 @@ func resourceArmDnsTxtRecordCreateOrUpdate(d *schema.ResourceData, meta interfac name := d.Get("name").(string) resGroup := d.Get("resource_group_name").(string) zoneName := d.Get("zone_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, zoneName, name, dns.TXT) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of DNS TXT Record %q (Zone %q / Resource Group %q): %+v", name, zoneName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_dns_txt_record", *resp.ID) + } + } + ttl := int64(d.Get("ttl").(int)) tags := d.Get("tags").(map[string]interface{}) @@ -82,7 +105,9 @@ func resourceArmDnsTxtRecordCreateOrUpdate(d *schema.ResourceData, meta interfac eTag := "" ifNoneMatch := "" // set to empty to allow updates to records after creation - resp, err := client.CreateOrUpdate(ctx, resGroup, zoneName, name, dns.TXT, parameters, eTag, ifNoneMatch) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + resp, err := client.CreateOrUpdate(waitCtx, resGroup, zoneName, name, dns.TXT, parameters, eTag, ifNoneMatch) if err != nil { return err } @@ -144,16 +169,18 @@ func resourceArmDnsTxtRecordDelete(d *schema.ResourceData, meta interface{}) err name := id.Path["TXT"] zoneName := id.Path["dnszones"] - resp, error := client.Delete(ctx, resGroup, zoneName, name, dns.TXT, "") + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resGroup, zoneName, name, dns.TXT, "") if resp.StatusCode != http.StatusOK { - return fmt.Errorf("Error deleting DNS TXT Record %s: %+v", name, error) + return fmt.Errorf("Error deleting DNS TXT Record %s: %+v", name, err) } return nil } func flattenAzureRmDnsTxtRecords(records *[]dns.TxtRecord) []map[string]interface{} { - results := make([]map[string]interface{}, 0, len(*records)) + results := make([]map[string]interface{}, 0) if records != nil { for _, record := range *records { diff --git a/azurerm/resource_arm_dns_txt_record_test.go b/azurerm/resource_arm_dns_txt_record_test.go index f97dcb26f5bb..edde41cb3904 100644 --- a/azurerm/resource_arm_dns_txt_record_test.go +++ b/azurerm/resource_arm_dns_txt_record_test.go @@ -31,6 +31,30 @@ func TestAccAzureRMDnsTxtRecord_basic(t *testing.T) { }) } +func TestAccAzureRMDnsTxtRecord_requiresImport(t *testing.T) { + resourceName := "azurerm_dns_txt_record.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDnsTxtRecordDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDnsTxtRecord_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDnsTxtRecordExists(resourceName), + ), + }, + { + Config: testAccAzureRMDnsTxtRecord_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_dns_txt_record"), + }, + }, + }) +} + func TestAccAzureRMDnsTxtRecord_updateRecords(t *testing.T) { resourceName := "azurerm_dns_txt_record.test" ri := acctest.RandInt() @@ -177,6 +201,28 @@ resource "azurerm_dns_txt_record" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMDnsTxtRecord_requiresImport(rInt int, location string) string { + template := testAccAzureRMDnsTxtRecord_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_dns_txt_record" "import" { + name = "${azurerm_dns_txt_record.test.name}" + resource_group_name = "${azurerm_dns_txt_record.test.resource_group_name}" + zone_name = "${azurerm_dns_txt_record.test.zone_name}" + ttl = 300 + + record { + value = "Quick brown fox" + } + + record { + value = "Another test txt string" + } +} +`, template) +} + func testAccAzureRMDnsTxtRecord_updateRecords(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_dns_zone.go b/azurerm/resource_arm_dns_zone.go index ecde8ec45691..8f46a7ed060b 100644 --- a/azurerm/resource_arm_dns_zone.go +++ b/azurerm/resource_arm_dns_zone.go @@ -1,12 +1,15 @@ package azurerm import ( + "context" "fmt" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/dns/mgmt/2018-03-01-preview/dns" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -19,6 +22,11 @@ func resourceArmDnsZone() *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": { @@ -79,6 +87,21 @@ func resourceArmDnsZoneCreateUpdate(d *schema.ResourceData, meta interface{}) er name := d.Get("name").(string) resGroup := d.Get("resource_group_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 DNS Zone %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_dns_zone", *resp.ID) + } + } + location := "global" zoneType := d.Get("zone_type").(string) tags := d.Get("tags").(map[string]interface{}) @@ -98,7 +121,9 @@ func resourceArmDnsZoneCreateUpdate(d *schema.ResourceData, meta interface{}) er etag := "" ifNoneMatch := "" // set to empty to allow updates to records after creation - resp, err := client.CreateOrUpdate(ctx, resGroup, name, parameters, etag, ifNoneMatch) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + resp, err := client.CreateOrUpdate(waitCtx, resGroup, name, parameters, etag, ifNoneMatch) if err != nil { return err } @@ -180,7 +205,9 @@ func resourceArmDnsZoneDelete(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error deleting DNS zone %s (resource group %s): %+v", name, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if response.WasNotFound(future.Response()) { return nil diff --git a/azurerm/resource_arm_dns_zone_test.go b/azurerm/resource_arm_dns_zone_test.go index 60322b7fefb7..9487a3b237e6 100644 --- a/azurerm/resource_arm_dns_zone_test.go +++ b/azurerm/resource_arm_dns_zone_test.go @@ -30,6 +30,30 @@ func TestAccAzureRMDnsZone_basic(t *testing.T) { }) } +func TestAccAzureRMDnsZone_requiresImport(t *testing.T) { + resourceName := "azurerm_dns_zone.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMDnsZoneDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMDnsZone_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMDnsZoneExists(resourceName), + ), + }, + { + Config: testAccAzureRMDnsZone_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_dns_zone"), + }, + }, + }) +} + func TestAccAzureRMDnsZone_withVNets(t *testing.T) { resourceName := "azurerm_dns_zone.test" ri := acctest.RandInt() @@ -150,6 +174,18 @@ resource "azurerm_dns_zone" "test" { `, rInt, location, rInt) } +func testAccAzureRMDnsZone_requiresImport(rInt int, location string) string { + template := testAccAzureRMDnsZone_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_dns_zone" "import" { + name = "${azurerm_dns_zone.test.name}" + resource_group_name = "${azurerm_dns_zone.test.resource_group_name}" +} +`, template) +} + func testAccAzureRMDnsZone_withVNets(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_eventgrid_topic.go b/azurerm/resource_arm_eventgrid_topic.go index 1eab582ab50f..e50c36f2a067 100644 --- a/azurerm/resource_arm_eventgrid_topic.go +++ b/azurerm/resource_arm_eventgrid_topic.go @@ -1,12 +1,15 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/eventgrid/mgmt/2018-01-01/eventgrid" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -19,6 +22,11 @@ func resourceArmEventGridTopic() *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": { @@ -58,8 +66,23 @@ func resourceArmEventGridTopicCreateUpdate(d *schema.ResourceData, meta interfac ctx := meta.(*ArmClient).StopContext name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resourceGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of EventGrid Topic %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_eventgrid_topic", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) tags := d.Get("tags").(map[string]interface{}) properties := eventgrid.Topic{ @@ -68,14 +91,16 @@ func resourceArmEventGridTopicCreateUpdate(d *schema.ResourceData, meta interfac Tags: expandTags(tags), } - log.Printf("[INFO] preparing arguments for AzureRM EventGrid Topic creation with Properties: %+v.", properties) + log.Printf("[INFO] preparing arguments for EventGrid Topic creation with Properties: %+v.", properties) future, err := client.CreateOrUpdate(ctx, resourceGroup, name, properties) if err != nil { return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -157,7 +182,9 @@ func resourceArmEventGridTopicDelete(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("Error deleting Event Grid Topic %q: %+v", name, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if response.WasNotFound(future.Response()) { return nil diff --git a/azurerm/resource_arm_eventgrid_topic_test.go b/azurerm/resource_arm_eventgrid_topic_test.go index cc77fe198d61..0b5bf8c4d14d 100644 --- a/azurerm/resource_arm_eventgrid_topic_test.go +++ b/azurerm/resource_arm_eventgrid_topic_test.go @@ -34,6 +34,29 @@ func TestAccAzureRMEventGridTopic_basic(t *testing.T) { }) } +func TestAccAzureRMEventGridTopic_requiresImport(t *testing.T) { + resourceName := "azurerm_eventgrid_topic.test" + ri := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMEventGridTopicDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMEventGridTopic_basic(ri), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMEventGridTopicExists(resourceName), + ), + }, + { + Config: testAccAzureRMEventGridTopic_requiresImport(ri), + ExpectError: testRequiresImportError("azurerm_eventgrid_topic"), + }, + }, + }) +} + func TestAccAzureRMEventGridTopic_basicWithTags(t *testing.T) { resourceName := "azurerm_eventgrid_topic.test" ri := acctest.RandInt() @@ -134,6 +157,19 @@ resource "azurerm_eventgrid_topic" "test" { `, rInt, location, rInt) } +func testAccAzureRMEventGridTopic_requiresImport(rInt int) string { + template := testAccAzureRMEventGridTopic_basic(rInt) + return fmt.Sprintf(` +%s + +resource "azurerm_eventgrid_topic" "import" { + name = "${azurerm_eventgrid_topic.test.name}" + location = "${azurerm_eventgrid_topic.test.location}" + resource_group_name = "${azurerm_eventgrid_topic.test.resource_group_name}" +} +`, template) +} + func testAccAzureRMEventGridTopic_basicWithTags(rInt int) string { // currently only supported in "West Central US" & "West US 2" location := "westus2" diff --git a/azurerm/resource_arm_eventhub.go b/azurerm/resource_arm_eventhub.go index 80f7774ecd6d..aad647f4b59f 100644 --- a/azurerm/resource_arm_eventhub.go +++ b/azurerm/resource_arm_eventhub.go @@ -1,8 +1,10 @@ package azurerm import ( + "context" "fmt" "log" + "time" "strings" @@ -10,18 +12,24 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) func resourceArmEventHub() *schema.Resource { return &schema.Resource{ - Create: resourceArmEventHubCreate, + Create: resourceArmEventHubCreateUpdate, Read: resourceArmEventHubRead, - Update: resourceArmEventHubCreate, + Update: resourceArmEventHubCreateUpdate, Delete: resourceArmEventHubDelete, 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": { @@ -132,7 +140,7 @@ func resourceArmEventHub() *schema.Resource { } } -func resourceArmEventHubCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmEventHubCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).eventHubClient ctx := meta.(*ArmClient).StopContext log.Printf("[INFO] preparing arguments for Azure ARM EventHub creation.") @@ -140,6 +148,21 @@ func resourceArmEventHubCreate(d *schema.ResourceData, meta interface{}) error { name := d.Get("name").(string) namespaceName := d.Get("namespace_name").(string) resourceGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, namespaceName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of EventHub %q (Namespace %q /Resource Group %q): %+v", name, namespaceName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_eventhub", *resp.ID) + } + } + partitionCount := int64(d.Get("partition_count").(int)) messageRetention := int64(d.Get("message_retention").(int)) @@ -159,7 +182,9 @@ func resourceArmEventHubCreate(d *schema.ResourceData, meta interface{}) error { parameters.Properties.CaptureDescription = captureDescription } - _, err := client.CreateOrUpdate(ctx, resourceGroup, namespaceName, name, parameters) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, resourceGroup, namespaceName, name, parameters) if err != nil { return err } @@ -228,8 +253,10 @@ func resourceArmEventHubDelete(d *schema.ResourceData, meta interface{}) error { resourceGroup := id.ResourceGroup namespaceName := id.Path["namespaces"] name := id.Path["eventhubs"] - resp, err := client.Delete(ctx, resourceGroup, namespaceName, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resourceGroup, namespaceName, name) if err != nil { if utils.ResponseWasNotFound(resp) { return nil diff --git a/azurerm/resource_arm_eventhub_authorization_rule.go b/azurerm/resource_arm_eventhub_authorization_rule.go index d9cfb33362c2..a871c3f16e0b 100644 --- a/azurerm/resource_arm_eventhub_authorization_rule.go +++ b/azurerm/resource_arm_eventhub_authorization_rule.go @@ -1,13 +1,16 @@ package azurerm import ( + "context" "fmt" "log" "net/http" + "time" "github.com/Azure/azure-sdk-for-go/services/eventhub/mgmt/2017-04-01/eventhub" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -17,10 +20,14 @@ func resourceArmEventHubAuthorizationRule() *schema.Resource { Read: resourceArmEventHubAuthorizationRuleRead, Update: resourceArmEventHubAuthorizationRuleCreateUpdate, Delete: resourceArmEventHubAuthorizationRuleDelete, - Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 10), + Update: schema.DefaultTimeout(time.Minute * 10), + Delete: schema.DefaultTimeout(time.Minute * 10), + }, Schema: azure.EventHubAuthorizationRuleSchemaFrom(map[string]*schema.Schema{ "name": { @@ -64,6 +71,20 @@ func resourceArmEventHubAuthorizationRuleCreateUpdate(d *schema.ResourceData, me eventHubName := d.Get("eventhub_name").(string) resourceGroup := d.Get("resource_group_name").(string) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.GetAuthorizationRule(ctx, resourceGroup, namespaceName, eventHubName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Authorization Rule %q (EventHub %q / Namespace %q / Resource Group %q): %+v", name, eventHubName, namespaceName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_eventhub_authorization_rule", *resp.ID) + } + } + parameters := eventhub.AuthorizationRule{ Name: &name, AuthorizationRuleProperties: &eventhub.AuthorizationRuleProperties{ @@ -71,7 +92,9 @@ func resourceArmEventHubAuthorizationRuleCreateUpdate(d *schema.ResourceData, me }, } - if _, err := client.CreateOrUpdateAuthorizationRule(ctx, resourceGroup, namespaceName, eventHubName, name, parameters); err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + if _, err := client.CreateOrUpdateAuthorizationRule(waitCtx, resourceGroup, namespaceName, eventHubName, name, parameters); err != nil { return fmt.Errorf("Error creating/updating EventHub Authorization Rule %q (Resource Group %q): %+v", name, resourceGroup, err) } @@ -151,7 +174,9 @@ func resourceArmEventHubAuthorizationRuleDelete(d *schema.ResourceData, meta int namespaceName := id.Path["namespaces"] eventHubName := id.Path["eventhubs"] - resp, err := eventhubClient.DeleteAuthorizationRule(ctx, resourceGroup, namespaceName, eventHubName, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := eventhubClient.DeleteAuthorizationRule(waitCtx, resourceGroup, namespaceName, eventHubName, name) if resp.StatusCode != http.StatusOK { return fmt.Errorf("Error issuing Azure ARM delete request of EventHub Authorization Rule '%s': %+v", name, err) diff --git a/azurerm/resource_arm_eventhub_authorization_rule_test.go b/azurerm/resource_arm_eventhub_authorization_rule_test.go index 99e5ddf0ef85..d82b8dbc138f 100644 --- a/azurerm/resource_arm_eventhub_authorization_rule_test.go +++ b/azurerm/resource_arm_eventhub_authorization_rule_test.go @@ -27,6 +27,31 @@ func TestAccAzureRMEventHubAuthorizationRule_manage(t *testing.T) { testAccAzureRMEventHubAuthorizationRule(t, true, true, true) } +func TestAccAzureRMEventHubAuthorizationRule_requiresImport(t *testing.T) { + resourceName := "azurerm_eventhub_authorization_rule.test" + + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMEventHubAuthorizationRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMEventHubAuthorizationRule_base(ri, location, true, true, true), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMEventHubAuthorizationRuleExists(resourceName), + ), + }, + { + Config: testAccAzureRMEventHubAuthorizationRule_requiresImport(ri, location, true, true, true), + ExpectError: testRequiresImportError("azurerm_eventhub_authorization_rule"), + }, + }, + }) +} + func testAccAzureRMEventHubAuthorizationRule(t *testing.T, listen, send, manage bool) { resourceName := "azurerm_eventhub_authorization_rule.test" @@ -193,3 +218,20 @@ resource "azurerm_eventhub_authorization_rule" "test" { } `, rInt, location, listen, send, manage) } + +func testAccAzureRMEventHubAuthorizationRule_requiresImport(rInt int, location string, listen, send, manage bool) string { + template := testAccAzureRMEventHubAuthorizationRule_base(rInt, location, listen, send, manage) + return fmt.Sprintf(` +%s + +resource "azurerm_eventhub_authorization_rule" "import" { + name = "${azurerm_eventhub_authorization_rule.test.name}" + namespace_name = "${azurerm_eventhub_authorization_rule.test.namespace_name}" + eventhub_name = "${azurerm_eventhub_authorization_rule.test.eventhub_name}" + resource_group_name = "${azurerm_eventhub_authorization_rule.test.resource_group_name}" + listen = "${azurerm_eventhub_authorization_rule.test.listen}" + send = "${azurerm_eventhub_authorization_rule.test.send}" + manage = "${azurerm_eventhub_authorization_rule.test.manage}" +} +`, template) +} diff --git a/azurerm/resource_arm_eventhub_consumer_group.go b/azurerm/resource_arm_eventhub_consumer_group.go index b80acaf8253c..635050a81241 100644 --- a/azurerm/resource_arm_eventhub_consumer_group.go +++ b/azurerm/resource_arm_eventhub_consumer_group.go @@ -1,13 +1,16 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/eventhub/mgmt/2017-04-01/eventhub" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -20,6 +23,11 @@ func resourceArmEventHubConsumerGroup() *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": { @@ -65,6 +73,21 @@ func resourceArmEventHubConsumerGroupCreateUpdate(d *schema.ResourceData, meta i namespaceName := d.Get("namespace_name").(string) eventHubName := d.Get("eventhub_name").(string) resGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, namespaceName, eventHubName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Consumer Group %q (EventHub %q / Namespace %q / Resource Group %q): %+v", name, eventHubName, namespaceName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_eventhub_consumer_group", *resp.ID) + } + } + userMetaData := d.Get("user_metadata").(string) parameters := eventhub.ConsumerGroup{ @@ -74,7 +97,9 @@ func resourceArmEventHubConsumerGroupCreateUpdate(d *schema.ResourceData, meta i }, } - _, err := client.CreateOrUpdate(ctx, resGroup, namespaceName, eventHubName, name, parameters) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, resGroup, namespaceName, eventHubName, name, parameters) if err != nil { return err } @@ -138,7 +163,9 @@ func resourceArmEventHubConsumerGroupDelete(d *schema.ResourceData, meta interfa eventHubName := id.Path["eventhubs"] name := id.Path["consumergroups"] - resp, err := client.Delete(ctx, resGroup, namespaceName, eventHubName, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + resp, err := client.Delete(waitCtx, resGroup, namespaceName, eventHubName, name) if err != nil { if !utils.ResponseWasNotFound(resp) { diff --git a/azurerm/resource_arm_eventhub_consumer_group_test.go b/azurerm/resource_arm_eventhub_consumer_group_test.go index 4d7bd62a0d79..a86458fd5162 100644 --- a/azurerm/resource_arm_eventhub_consumer_group_test.go +++ b/azurerm/resource_arm_eventhub_consumer_group_test.go @@ -11,7 +11,6 @@ import ( ) func TestAccAzureRMEventHubConsumerGroup_basic(t *testing.T) { - ri := acctest.RandInt() config := testAccAzureRMEventHubConsumerGroup_basic(ri, testLocation()) @@ -30,8 +29,30 @@ func TestAccAzureRMEventHubConsumerGroup_basic(t *testing.T) { }) } -func TestAccAzureRMEventHubConsumerGroup_complete(t *testing.T) { +func TestAccAzureRMEventHubConsumerGroup_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMEventHubConsumerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMEventHubConsumerGroup_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMEventHubConsumerGroupExists("azurerm_eventhub_consumer_group.test"), + ), + }, + { + Config: testAccAzureRMEventHubConsumerGroup_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_eventhub_consumer_group"), + }, + }, + }) +} + +func TestAccAzureRMEventHubConsumerGroup_complete(t *testing.T) { ri := acctest.RandInt() config := testAccAzureRMEventHubConsumerGroup_complete(ri, testLocation()) @@ -168,6 +189,20 @@ resource "azurerm_eventhub_consumer_group" "test" { `, rInt, location, rInt, rInt, rInt) } +func testAccAzureRMEventHubConsumerGroup_requiresImport(rInt int, location string) string { + template := testAccAzureRMEventHubConsumerGroup_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_eventhub_consumer_group" "import" { + name = "${azurerm_eventhub_consumer_group.test.name}" + namespace_name = "${azurerm_eventhub_consumer_group.test.namespace_name}" + eventhub_name = "${azurerm_eventhub_consumer_group.test.eventhub_name}" + resource_group_name = "${azurerm_eventhub_consumer_group.test.resource_group_name}" +} +`, template) +} + func testAccAzureRMEventHubConsumerGroup_complete(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_eventhub_namespace.go b/azurerm/resource_arm_eventhub_namespace.go index e2e868c4099d..5b07de527c3a 100644 --- a/azurerm/resource_arm_eventhub_namespace.go +++ b/azurerm/resource_arm_eventhub_namespace.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -22,13 +23,18 @@ var eventHubNamespaceDefaultAuthorizationRule = "RootManageSharedAccessKey" func resourceArmEventHubNamespace() *schema.Resource { return &schema.Resource{ - Create: resourceArmEventHubNamespaceCreate, + Create: resourceArmEventHubNamespaceCreateUpdate, Read: resourceArmEventHubNamespaceRead, - Update: resourceArmEventHubNamespaceCreate, + Update: resourceArmEventHubNamespaceCreateUpdate, Delete: resourceArmEventHubNamespaceDelete, 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": { @@ -101,14 +107,29 @@ func resourceArmEventHubNamespace() *schema.Resource { } } -func resourceArmEventHubNamespaceCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmEventHubNamespaceCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).eventHubNamespacesClient ctx := meta.(*ArmClient).StopContext log.Printf("[INFO] preparing arguments for AzureRM EventHub Namespace creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_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 EventHub Namespace %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_eventhub_namespace", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) sku := d.Get("sku").(string) capacity := int32(d.Get("capacity").(int)) tags := d.Get("tags").(map[string]interface{}) @@ -138,7 +159,9 @@ func resourceArmEventHubNamespaceCreate(d *schema.ResourceData, meta interface{} return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error creating eventhub namespace: %+v", err) } @@ -225,17 +248,20 @@ func resourceArmEventHubNamespaceDelete(d *schema.ResourceData, meta interface{} return fmt.Errorf("Error issuing delete request of EventHub Namespace %q (Resource Group %q): %+v", name, resGroup, err) } - return waitForEventHubNamespaceToBeDeleted(ctx, client, resGroup, name) + timeout := d.Timeout(schema.TimeoutDelete) + return waitForEventHubNamespaceToBeDeleted(ctx, client, resGroup, name, timeout) } -func waitForEventHubNamespaceToBeDeleted(ctx context.Context, client eventhub.NamespacesClient, resourceGroup, name string) error { +func waitForEventHubNamespaceToBeDeleted(ctx context.Context, client eventhub.NamespacesClient, resourceGroup, name string, timeout time.Duration) error { // we can't use the Waiter here since the API returns a 200 once it's deleted which is considered a polling status code.. log.Printf("[DEBUG] Waiting for EventHub Namespace (%q in Resource Group %q) to be deleted", name, resourceGroup) + waitCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() stateConf := &resource.StateChangeConf{ Pending: []string{"200"}, Target: []string{"404"}, - Refresh: eventHubNamespaceStateStatusCodeRefreshFunc(ctx, client, resourceGroup, name), - Timeout: 40 * time.Minute, + Refresh: eventHubNamespaceStateStatusCodeRefreshFunc(waitCtx, client, resourceGroup, name), + Timeout: timeout, } if _, err := stateConf.WaitForState(); err != nil { return fmt.Errorf("Error waiting for EventHub NameSpace (%q in Resource Group %q) to be deleted: %+v", name, resourceGroup, err) diff --git a/azurerm/resource_arm_eventhub_namespace_authorization_rule.go b/azurerm/resource_arm_eventhub_namespace_authorization_rule.go index 9a59032abb7d..1ecdeb299557 100644 --- a/azurerm/resource_arm_eventhub_namespace_authorization_rule.go +++ b/azurerm/resource_arm_eventhub_namespace_authorization_rule.go @@ -1,13 +1,16 @@ package azurerm import ( + "context" "fmt" "log" "net/http" + "time" "github.com/Azure/azure-sdk-for-go/services/eventhub/mgmt/2017-04-01/eventhub" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -17,10 +20,14 @@ func resourceArmEventHubNamespaceAuthorizationRule() *schema.Resource { Read: resourceArmEventHubNamespaceAuthorizationRuleRead, Update: resourceArmEventHubNamespaceAuthorizationRuleCreateUpdate, Delete: resourceArmEventHubNamespaceAuthorizationRuleDelete, - 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: azure.EventHubAuthorizationRuleSchemaFrom(map[string]*schema.Schema{ "name": { @@ -56,6 +63,20 @@ func resourceArmEventHubNamespaceAuthorizationRuleCreateUpdate(d *schema.Resourc namespaceName := d.Get("namespace_name").(string) resourceGroup := d.Get("resource_group_name").(string) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.GetAuthorizationRule(ctx, resourceGroup, namespaceName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Authorization Rule %q (Namespace %q / Resource Group %q): %+v", name, namespaceName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_eventhub_namespace_authorization_rule", *resp.ID) + } + } + parameters := eventhub.AuthorizationRule{ Name: &name, AuthorizationRuleProperties: &eventhub.AuthorizationRuleProperties{ @@ -63,7 +84,9 @@ func resourceArmEventHubNamespaceAuthorizationRuleCreateUpdate(d *schema.Resourc }, } - if _, err := client.CreateOrUpdateAuthorizationRule(ctx, resourceGroup, namespaceName, name, parameters); err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + if _, err := client.CreateOrUpdateAuthorizationRule(waitCtx, resourceGroup, namespaceName, name, parameters); err != nil { return fmt.Errorf("Error creating/updating EventHub Namespace Authorization Rule %q (Resource Group %q): %+v", name, resourceGroup, err) } @@ -140,7 +163,9 @@ func resourceArmEventHubNamespaceAuthorizationRuleDelete(d *schema.ResourceData, resourceGroup := id.ResourceGroup namespaceName := id.Path["namespaces"] - resp, err := eventhubClient.DeleteAuthorizationRule(ctx, resourceGroup, namespaceName, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := eventhubClient.DeleteAuthorizationRule(waitCtx, resourceGroup, namespaceName, name) if resp.StatusCode != http.StatusOK { return fmt.Errorf("Error issuing Azure ARM delete request of EventHub Authorization Rule '%s': %+v", name, err) diff --git a/azurerm/resource_arm_eventhub_namespace_authorization_rule_test.go b/azurerm/resource_arm_eventhub_namespace_authorization_rule_test.go index c718558ec67b..775f76bf5b65 100644 --- a/azurerm/resource_arm_eventhub_namespace_authorization_rule_test.go +++ b/azurerm/resource_arm_eventhub_namespace_authorization_rule_test.go @@ -59,6 +59,30 @@ func testAccAzureRMEventHubNamespaceAuthorizationRule(t *testing.T, listen, send }) } +func TestAccAzureRMEventHubNamespaceAuthorizationRule_requiresImport(t *testing.T) { + resourceName := "azurerm_eventhub_namespace_authorization_rule.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMEventHubNamespaceAuthorizationRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMEventHubNamespaceAuthorizationRule_base(ri, location, true, true, true), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMEventHubNamespaceAuthorizationRuleExists(resourceName), + ), + }, + { + Config: testAccAzureRMEventHubNamespaceAuthorizationRule_requiresImport(ri, location, true, true, true), + ExpectError: testRequiresImportError("azurerm_eventhub_namespace_authorization_rule"), + }, + }, + }) +} + func TestAccAzureRMEventHubNamespaceAuthorizationRule_rightsUpdate(t *testing.T) { resourceName := "azurerm_eventhub_namespace_authorization_rule.test" @@ -165,7 +189,6 @@ resource "azurerm_eventhub_namespace" "test" { name = "acctesteventhubnamespace-%[1]d" location = "${azurerm_resource_group.test.location}" resource_group_name = "${azurerm_resource_group.test.name}" - sku = "Standard" } @@ -180,3 +203,19 @@ resource "azurerm_eventhub_namespace_authorization_rule" "test" { } `, rInt, location, listen, send, manage) } + +func testAccAzureRMEventHubNamespaceAuthorizationRule_requiresImport(rInt int, location string, listen, send, manage bool) string { + template := testAccAzureRMEventHubNamespaceAuthorizationRule_base(rInt, location, listen, send, manage) + return fmt.Sprintf(` +%s + +resource "azurerm_eventhub_namespace_authorization_rule" "import" { + name = "${azurerm_eventhub_namespace_authorization_rule.test.name}" + namespace_name = "${azurerm_eventhub_namespace_authorization_rule.test.namespace_name}" + resource_group_name = "${azurerm_eventhub_namespace_authorization_rule.test.resource_group_name}" + listen = "${azurerm_eventhub_namespace_authorization_rule.test.listen}" + manage = "${azurerm_eventhub_namespace_authorization_rule.test.manage}" + send = "${azurerm_eventhub_namespace_authorization_rule.test.send}" +} +`, template) +} diff --git a/azurerm/resource_arm_eventhub_namespace_test.go b/azurerm/resource_arm_eventhub_namespace_test.go index 978ef5e5a9b7..49609f37ce26 100644 --- a/azurerm/resource_arm_eventhub_namespace_test.go +++ b/azurerm/resource_arm_eventhub_namespace_test.go @@ -30,6 +30,29 @@ func TestAccAzureRMEventHubNamespace_basic(t *testing.T) { }) } +func TestAccAzureRMEventHubNamespace_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMEventHubNamespaceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMEventHubNamespace_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMEventHubNamespaceExists("azurerm_eventhub_namespace.test"), + ), + }, + { + Config: testAccAzureRMEventHubNamespace_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_eventhub_namespace"), + }, + }, + }) +} + func TestAccAzureRMEventHubNamespace_standard(t *testing.T) { ri := acctest.RandInt() config := testAccAzureRMEventHubNamespace_standard(ri, testLocation()) @@ -329,6 +352,20 @@ resource "azurerm_eventhub_namespace" "test" { `, rInt, location, rInt) } +func testAccAzureRMEventHubNamespace_requiresImport(rInt int, location string) string { + template := testAccAzureRMEventHubNamespace_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_eventhub_namespace" "import" { + name = "${azurerm_eventhub_namespace.test.name}" + location = "${azurerm_eventhub_namespace.test.location}" + resource_group_name = "${azurerm_eventhub_namespace.test.resource_group_name}" + sku = "${azurerm_eventhub_namespace.test.sku}" +} +`, template) +} + func testAccAzureRMEventHubNamespace_standard(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_eventhub_test.go b/azurerm/resource_arm_eventhub_test.go index b224e06689cc..a820ea0e96da 100644 --- a/azurerm/resource_arm_eventhub_test.go +++ b/azurerm/resource_arm_eventhub_test.go @@ -184,6 +184,30 @@ func TestAccAzureRMEventHub_basic(t *testing.T) { }) } +func TestAccAzureRMEventHub_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMEventHubDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMEventHub_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMEventHubExists("azurerm_eventhub.test"), + ), + }, + { + + Config: testAccAzureRMEventHub_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_eventhub"), + }, + }, + }) +} + func TestAccAzureRMEventHub_standard(t *testing.T) { ri := acctest.RandInt() @@ -369,6 +393,21 @@ resource "azurerm_eventhub" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMEventHub_requiresImport(rInt int, location string) string { + template := testAccAzureRMEventHub_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_eventhub" "import" { + name = "${azurerm_eventhub.test.name}" + namespace_name = "${azurerm_eventhub.test.namespace_name}" + resource_group_name = "${azurerm_eventhub.test.resource_group_name} + partition_count = 2 + message_retention = 1 +} +`, template) +} + func testAccAzureRMEventHub_standard(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_express_route_circuit.go b/azurerm/resource_arm_express_route_circuit.go index 7c5ae66b123d..1669752d5058 100644 --- a/azurerm/resource_arm_express_route_circuit.go +++ b/azurerm/resource_arm_express_route_circuit.go @@ -1,12 +1,16 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" "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" ) var expressRouteCircuitResourceName = "azurerm_express_route_circuit" @@ -20,6 +24,11 @@ func resourceArmExpressRouteCircuit() *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": { @@ -110,6 +119,21 @@ func resourceArmExpressRouteCircuitCreateOrUpdate(d *schema.ResourceData, meta i name := d.Get("name").(string) resGroup := d.Get("resource_group_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 ExpressRouteCircuit %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_express_route_circuit", *resp.ID) + } + } + location := azureRMNormalizeLocation(d.Get("location").(string)) serviceProviderName := d.Get("service_provider_name").(string) peeringLocation := d.Get("peering_location").(string) @@ -142,7 +166,9 @@ func resourceArmExpressRouteCircuitCreateOrUpdate(d *schema.ResourceData, meta i return fmt.Errorf("Error Creating/Updating ExpressRouteCircuit %q (Resource Group %q): %+v", name, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error Creating/Updating ExpressRouteCircuit %q (Resource Group %q): %+v", name, resGroup, err) } @@ -161,15 +187,26 @@ func resourceArmExpressRouteCircuitCreateOrUpdate(d *schema.ResourceData, meta i } func resourceArmExpressRouteCircuitRead(d *schema.ResourceData, meta interface{}) error { - resp, resourceGroup, err := retrieveErcByResourceId(d.Id(), meta) + client := meta.(*ArmClient).expressRouteCircuitClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) if err != nil { return err } - if resp == nil { - log.Printf("[INFO] Express Route Circuit %q not found. Removing from state", d.Get("name").(string)) - d.SetId("") - return nil + resourceGroup := id.ResourceGroup + name := id.Path["expressRouteCircuits"] + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] Express Route Circuit %q not found. Removing from state", name) + d.SetId("") + return nil + } + + return fmt.Errorf("Error making Read request on Express Route Circuit %s: %+v", name, err) } d.Set("name", resp.Name) @@ -191,9 +228,11 @@ func resourceArmExpressRouteCircuitRead(d *schema.ResourceData, meta interface{} d.Set("bandwidth_in_mbps", props.BandwidthInMbps) } - d.Set("service_provider_provisioning_state", string(resp.ServiceProviderProvisioningState)) - d.Set("service_key", resp.ServiceKey) - d.Set("allow_classic_operations", resp.AllowClassicOperations) + if props := resp.ExpressRouteCircuitPropertiesFormat; props != nil { + d.Set("service_provider_provisioning_state", string(props.ServiceProviderProvisioningState)) + d.Set("service_key", props.ServiceKey) + d.Set("allow_classic_operations", props.AllowClassicOperations) + } flattenAndSetTags(d, resp.Tags) @@ -204,10 +243,12 @@ func resourceArmExpressRouteCircuitDelete(d *schema.ResourceData, meta interface client := meta.(*ArmClient).expressRouteCircuitClient ctx := meta.(*ArmClient).StopContext - resourceGroup, name, err := extractResourceGroupAndErcName(d.Id()) + id, err := parseAzureResourceID(d.Id()) if err != nil { - return fmt.Errorf("Error Parsing Azure Resource ID: %+v", err) + return err } + resourceGroup := id.ResourceGroup + name := id.Path["expressRouteCircuits"] azureRMLockByName(name, expressRouteCircuitResourceName) defer azureRMUnlockByName(name, expressRouteCircuitResourceName) @@ -217,7 +258,9 @@ func resourceArmExpressRouteCircuitDelete(d *schema.ResourceData, meta interface return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } diff --git a/azurerm/resource_arm_express_route_circuit_authorization.go b/azurerm/resource_arm_express_route_circuit_authorization.go index 1f0f07a59728..faa295b50854 100644 --- a/azurerm/resource_arm_express_route_circuit_authorization.go +++ b/azurerm/resource_arm_express_route_circuit_authorization.go @@ -1,22 +1,29 @@ package azurerm import ( + "context" "fmt" + "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) func resourceArmExpressRouteCircuitAuthorization() *schema.Resource { return &schema.Resource{ - Create: resourceArmExpressRouteCircuitAuthorizationCreateUpdate, + Create: resourceArmExpressRouteCircuitAuthorizationCreate, Read: resourceArmExpressRouteCircuitAuthorizationRead, Delete: resourceArmExpressRouteCircuitAuthorizationDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Schema: map[string]*schema.Schema{ "name": { @@ -47,7 +54,7 @@ func resourceArmExpressRouteCircuitAuthorization() *schema.Resource { } } -func resourceArmExpressRouteCircuitAuthorizationCreateUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceArmExpressRouteCircuitAuthorizationCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).expressRouteAuthsClient ctx := meta.(*ArmClient).StopContext @@ -55,6 +62,18 @@ func resourceArmExpressRouteCircuitAuthorizationCreateUpdate(d *schema.ResourceD resourceGroup := d.Get("resource_group_name").(string) circuitName := d.Get("express_route_circuit_name").(string) + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, circuitName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Authorization %q (Express Route Circuit %q / Resource Group %q): %+v", name, circuitName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_express_route_circuit_authorization", *resp.ID) + } + properties := network.ExpressRouteCircuitAuthorization{ AuthorizationPropertiesFormat: &network.AuthorizationPropertiesFormat{}, } @@ -67,7 +86,9 @@ func resourceArmExpressRouteCircuitAuthorizationCreateUpdate(d *schema.ResourceD return fmt.Errorf("Error Creating/Updating Express Route Circuit Authorization %q (Circuit %q / Resource Group %q): %+v", name, circuitName, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for Express Route Circuit Authorization %q (Circuit %q / Resource Group %q) to finish creating/updating: %+v", name, circuitName, resourceGroup, err) } @@ -141,7 +162,9 @@ func resourceArmExpressRouteCircuitAuthorizationDelete(d *schema.ResourceData, m return fmt.Errorf("Error deleting Express Route Circuit Authorization %q (Circuit %q / Resource Group %q): %+v", name, circuitName, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if response.WasNotFound(future.Response()) { return nil diff --git a/azurerm/resource_arm_express_route_circuit_authorization_test.go b/azurerm/resource_arm_express_route_circuit_authorization_test.go index 65e52e180146..a8d927211f86 100644 --- a/azurerm/resource_arm_express_route_circuit_authorization_test.go +++ b/azurerm/resource_arm_express_route_circuit_authorization_test.go @@ -31,6 +31,31 @@ func testAccAzureRMExpressRouteCircuitAuthorization_basic(t *testing.T) { }) } +func testAccAzureRMExpressRouteCircuitAuthorization_requiresImport(t *testing.T) { + resourceName := "azurerm_express_route_circuit_authorization.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMExpressRouteCircuitAuthorizationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMExpressRouteCircuitAuthorization_basicConfig(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMExpressRouteCircuitAuthorizationExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "authorization_key"), + ), + }, + { + Config: testAccAzureRMExpressRouteCircuitAuthorization_requiresImportConfig(ri, location), + ExpectError: testRequiresImportError("azurerm_express_route_circuit_authorization"), + }, + }, + }) +} + func testAccAzureRMExpressRouteCircuitAuthorization_multiple(t *testing.T) { firstResourceName := "azurerm_express_route_circuit_authorization.test1" secondResourceName := "azurerm_express_route_circuit_authorization.test2" @@ -147,6 +172,19 @@ resource "azurerm_express_route_circuit_authorization" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMExpressRouteCircuitAuthorization_requiresImportConfig(rInt int, location string) string { + template := testAccAzureRMExpressRouteCircuitAuthorization_basicConfig(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_express_route_circuit_authorization" "import" { + name = "${azurerm_express_route_circuit_authorization.test.name}" + express_route_circuit_name = "${azurerm_express_route_circuit_authorization.test.express_route_circuit_name}" + resource_group_name = "${azurerm_express_route_circuit_authorization.test.resource_group_name}" +} +`, template) +} + func testAccAzureRMExpressRouteCircuitAuthorization_multipleConfig(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_express_route_circuit_peering.go b/azurerm/resource_arm_express_route_circuit_peering.go index 5fd72ce25a0d..c4a01b179d9b 100644 --- a/azurerm/resource_arm_express_route_circuit_peering.go +++ b/azurerm/resource_arm_express_route_circuit_peering.go @@ -1,14 +1,17 @@ package azurerm import ( + "context" "fmt" "log" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -21,6 +24,11 @@ func resourceArmExpressRouteCircuitPeering() *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{ "peering_type": { @@ -112,6 +120,20 @@ func resourceArmExpressRouteCircuitPeeringCreateUpdate(d *schema.ResourceData, m circuitName := d.Get("express_route_circuit_name").(string) resourceGroup := d.Get("resource_group_name").(string) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, circuitName, peeringType) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Peering %q (ExpressRoute Circuit %q / Resource Group %q): %+v", peeringType, circuitName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_express_route_circuit_peering", *resp.ID) + } + } + sharedKey := d.Get("shared_key").(string) primaryPeerAddressPrefix := d.Get("primary_peer_address_prefix").(string) secondaryPeerAddressPrefix := d.Get("secondary_peer_address_prefix").(string) @@ -149,7 +171,9 @@ func resourceArmExpressRouteCircuitPeeringCreateUpdate(d *schema.ResourceData, m return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -231,7 +255,9 @@ func resourceArmExpressRouteCircuitPeeringDelete(d *schema.ResourceData, meta in return fmt.Errorf("Error issuing delete request for Express Route Circuit Peering %q (Circuit %q / Resource Group %q): %+v", peeringType, circuitName, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if response.WasNotFound(future.Response()) { return nil diff --git a/azurerm/resource_arm_express_route_circuit_peering_test.go b/azurerm/resource_arm_express_route_circuit_peering_test.go index 9e22566a6508..3e6e270962b0 100644 --- a/azurerm/resource_arm_express_route_circuit_peering_test.go +++ b/azurerm/resource_arm_express_route_circuit_peering_test.go @@ -33,6 +33,30 @@ func testAccAzureRMExpressRouteCircuitPeering_azurePrivatePeering(t *testing.T) }) } +func testAccAzureRMExpressRouteCircuitPeering_requiresImport(t *testing.T) { + resourceName := "azurerm_express_route_circuit_peering.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMExpressRouteCircuitPeeringDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMExpressRouteCircuitPeering_privatePeering(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMExpressRouteCircuitPeeringExists(resourceName), + ), + }, + { + Config: testAccAzureRMExpressRouteCircuitPeering_requiresImportConfig(ri, location), + ExpectError: testRequiresImportError("azurerm_express_route_circuit_peering"), + }, + }, + }) +} + func testAccAzureRMExpressRouteCircuitPeering_microsoftPeering(t *testing.T) { resourceName := "azurerm_express_route_circuit_peering.test" ri := acctest.RandInt() @@ -151,6 +175,24 @@ resource "azurerm_express_route_circuit_peering" "test" { `, rInt, location, rInt) } +func testAccAzureRMExpressRouteCircuitPeering_requiresImportConfig(rInt int, location string) string { + template := testAccAzureRMExpressRouteCircuitPeering_privatePeering(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_express_route_circuit_peering" "import" { + peering_type = "${azurerm_express_route_circuit_peering.test.peering_type}" + express_route_circuit_name = "${azurerm_express_route_circuit_peering.test.express_route_circuit_name}" + resource_group_name = "${azurerm_express_route_circuit_peering.test.resource_group_name}" + shared_key = "${azurerm_express_route_circuit_peering.test.shared_key}" + peer_asn = "${azurerm_express_route_circuit_peering.test.peer_asn}" + primary_peer_address_prefix = "${azurerm_express_route_circuit_peering.test.primary_peer_address_prefix}" + secondary_peer_address_prefix = "${azurerm_express_route_circuit_peering.test.secondary_peer_address_prefix}" + vlan_id = "${azurerm_express_route_circuit_peering.test.vlan_id}" +} +`, template) +} + func testAccAzureRMExpressRouteCircuitPeering_msPeering(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_express_route_circuit_test.go b/azurerm/resource_arm_express_route_circuit_test.go index 746f166bd690..26aed9ddd567 100644 --- a/azurerm/resource_arm_express_route_circuit_test.go +++ b/azurerm/resource_arm_express_route_circuit_test.go @@ -24,19 +24,22 @@ func TestAccAzureRMExpressRouteCircuit(t *testing.T) { "premiumMetered": testAccAzureRMExpressRouteCircuit_premiumMetered, "premiumUnlimited": testAccAzureRMExpressRouteCircuit_premiumUnlimited, "allowClassicOperationsUpdate": testAccAzureRMExpressRouteCircuit_allowClassicOperationsUpdate, + "requiresImport": testAccAzureRMExpressRouteCircuit_requiresImport, }, "PrivatePeering": { "azurePrivatePeering": testAccAzureRMExpressRouteCircuitPeering_azurePrivatePeering, "importPrivatePeering": testAccAzureRMExpressRouteCircuitPeering_importAzurePrivatePeering, + "requiresImport": testAccAzureRMExpressRouteCircuitPeering_requiresImport, }, "MicrosoftPeering": { "microsoftPeering": testAccAzureRMExpressRouteCircuitPeering_microsoftPeering, "importMicrosoftPeering": testAccAzureRMExpressRouteCircuitPeering_importMicrosoftPeering, }, "authorization": { - "basic": testAccAzureRMExpressRouteCircuitAuthorization_basic, - "multiple": testAccAzureRMExpressRouteCircuitAuthorization_multiple, - "import": testAccAzureRMExpressRouteCircuitAuthorization_importBasic, + "basic": testAccAzureRMExpressRouteCircuitAuthorization_basic, + "requiresImport": testAccAzureRMExpressRouteCircuitAuthorization_requiresImport, + "multiple": testAccAzureRMExpressRouteCircuitAuthorization_multiple, + "import": testAccAzureRMExpressRouteCircuitAuthorization_importBasic, }, "authorizationImport": { "basic": testAccAzureRMExpressRouteCircuitAuthorization_importBasic, @@ -79,6 +82,30 @@ func testAccAzureRMExpressRouteCircuit_basicMetered(t *testing.T) { }) } +func testAccAzureRMExpressRouteCircuit_requiresImport(t *testing.T) { + var erc network.ExpressRouteCircuit + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMExpressRouteCircuitDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMExpressRouteCircuit_basicMeteredConfig(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMExpressRouteCircuitExists("azurerm_express_route_circuit.test", &erc), + ), + }, + { + Config: testAccAzureRMExpressRouteCircuit_requiresImportConfig(ri, location), + ExpectError: testRequiresImportError("azurerm_express_route_circuit"), + }, + }, + }) +} + func testAccAzureRMExpressRouteCircuit_basicUnlimited(t *testing.T) { var erc network.ExpressRouteCircuit ri := acctest.RandInt() @@ -313,6 +340,34 @@ resource "azurerm_express_route_circuit" "test" { `, rInt, location, rInt) } +func testAccAzureRMExpressRouteCircuit_requiresImportConfig(rInt int, location string) string { + template := testAccAzureRMExpressRouteCircuit_basicMeteredConfig(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_express_route_circuit" "import" { + name = "${azurerm_express_route_circuit.test.name}" + location = "${azurerm_express_route_circuit.test.location}" + resource_group_name = "${azurerm_express_route_circuit.test.resource_group_name}" + service_provider_name = "${azurerm_express_route_circuit.test.service_provider_name}" + peering_location = "${azurerm_express_route_circuit.test.peering_location}" + bandwidth_in_mbps = "${azurerm_express_route_circuit.test.bandwidth_in_mbps}" + + sku { + tier = "Standard" + family = "MeteredData" + } + + allow_classic_operations = false + + tags { + Environment = "production" + Purpose = "AcceptanceTests" + } +} +`, template) +} + func testAccAzureRMExpressRouteCircuit_basicUnlimitedConfig(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_function_app.go b/azurerm/resource_arm_function_app.go index 071ae1084297..ff9f3a1854cd 100644 --- a/azurerm/resource_arm_function_app.go +++ b/azurerm/resource_arm_function_app.go @@ -5,10 +5,12 @@ import ( "fmt" "log" "strings" + "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" ) @@ -23,6 +25,11 @@ func resourceArmFunctionApp() *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": { @@ -209,7 +216,21 @@ func resourceArmFunctionAppCreate(d *schema.ResourceData, meta interface{}) erro log.Printf("[INFO] preparing arguments for AzureRM Function App 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 Function App %q (Resource Group %q): %+v", name, resGroup, err) + } + } + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_function_app", *resp.ID) + } + + // then check if it's available globally availabilityRequest := web.ResourceNameAvailabilityRequest{ Name: utils.String(name), Type: web.CheckNameResourceTypesMicrosoftWebsites, @@ -223,7 +244,6 @@ func resourceArmFunctionAppCreate(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("The name %q used for the Function App 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)) kind := "functionapp" appServicePlanID := d.Get("app_service_plan_id").(string) @@ -260,12 +280,14 @@ func resourceArmFunctionAppCreate(d *schema.ResourceData, meta interface{}) erro } } - createFuture, err := client.CreateOrUpdate(ctx, resGroup, name, siteEnvelope) + future, err := client.CreateOrUpdate(ctx, resGroup, name, siteEnvelope) if err != nil { return err } - err = createFuture.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -304,10 +326,10 @@ func resourceArmFunctionAppUpdate(d *schema.ResourceData, meta interface{}) erro tags := d.Get("tags").(map[string]interface{}) appServiceTier, err := getFunctionAppServiceTier(ctx, appServicePlanID, meta) - if err != nil { return err } + basicAppSettings := getBasicFunctionAppAppSettings(d, appServiceTier) siteConfig := expandFunctionAppSiteConfig(d) siteConfig.AppSettings = &basicAppSettings @@ -336,7 +358,10 @@ func resourceArmFunctionAppUpdate(d *schema.ResourceData, meta interface{}) erro return err } - err = future.WaitForCompletionRef(ctx, client.Client) + // NOTE: we use tf.TimeoutForCreateUpdate here since Create calls Update to ensure we're using the right timeout + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -356,7 +381,7 @@ func resourceArmFunctionAppUpdate(d *schema.ResourceData, meta interface{}) erro 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 Function App %q: %+v", name, err) } @@ -369,7 +394,7 @@ func resourceArmFunctionAppUpdate(d *schema.ResourceData, meta interface{}) erro 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) } @@ -495,7 +520,9 @@ func resourceArmFunctionAppDelete(d *schema.ResourceData, meta interface{}) erro 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_function_app_test.go b/azurerm/resource_arm_function_app_test.go index 640304a7dcf7..cafe69768104 100644 --- a/azurerm/resource_arm_function_app_test.go +++ b/azurerm/resource_arm_function_app_test.go @@ -35,6 +35,31 @@ func TestAccAzureRMFunctionApp_basic(t *testing.T) { }) } +func TestAccAzureRMFunctionApp_requiresImport(t *testing.T) { + resourceName := "azurerm_function_app.test" + ri := acctest.RandInt() + rs := strings.ToLower(acctest.RandString(11)) + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMFunctionAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMFunctionApp_basic(ri, rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFunctionAppExists(resourceName), + ), + }, + { + Config: testAccAzureRMFunctionApp_requiresImport(ri, rs, location), + ExpectError: testRequiresImportError("azurerm_function_app"), + }, + }, + }) +} + func TestAccAzureRMFunctionApp_tags(t *testing.T) { resourceName := "azurerm_function_app.test" ri := acctest.RandInt() @@ -580,7 +605,23 @@ resource "azurerm_function_app" "test" { resource_group_name = "${azurerm_resource_group.test.name}" app_service_plan_id = "${azurerm_app_service_plan.test.id}" storage_connection_string = "${azurerm_storage_account.test.primary_connection_string}" -}`, rInt, location, storage) +} +`, rInt, location, storage) +} + +func testAccAzureRMFunctionApp_requiresImport(rInt int, storage string, location string) string { + template := testAccAzureRMFunctionApp_basic(rInt, storage, location) + return fmt.Sprintf(` +%s + +resource "azurerm_function_app" "import" { + name = "${azurerm_function_app.test.name}" + location = "${azurerm_function_app.test.location}" + resource_group_name = "${azurerm_function_app.test.resource_group_name}" + app_service_plan_id = "${azurerm_function_app.test.app_service_plan_id}" + storage_connection_string = "${azurerm_function_app.test.storage_connection_string}" +} +`, template) } func testAccAzureRMFunctionApp_tags(rInt int, storage string, location string) string { diff --git a/azurerm/resource_arm_image.go b/azurerm/resource_arm_image.go index c1c462ed5eac..655aa2a4fff8 100644 --- a/azurerm/resource_arm_image.go +++ b/azurerm/resource_arm_image.go @@ -1,10 +1,13 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-06-01/compute" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" @@ -22,6 +25,11 @@ func resourceArmImage() *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": { @@ -164,10 +172,24 @@ func resourceArmImageCreateUpdate(d *schema.ResourceData, meta interface{}) erro log.Printf("[INFO] preparing arguments for AzureRM Image creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_name").(string) - expandedTags := expandTags(d.Get("tags").(map[string]interface{})) + 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 Image %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_image", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) + expandedTags := expandTags(d.Get("tags").(map[string]interface{})) properties := compute.ImageProperties{} osDisk, err := expandAzureRmImageOsDisk(d) @@ -222,7 +244,9 @@ func resourceArmImageCreateUpdate(d *schema.ResourceData, meta interface{}) erro return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -304,7 +328,9 @@ func resourceArmImageDelete(d *schema.ResourceData, meta interface{}) error { return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } diff --git a/azurerm/resource_arm_image_test.go b/azurerm/resource_arm_image_test.go index 040d7fdd4617..e8ed08dc937e 100644 --- a/azurerm/resource_arm_image_test.go +++ b/azurerm/resource_arm_image_test.go @@ -49,6 +49,43 @@ func TestAccAzureRMImage_standaloneImage(t *testing.T) { }) } +func TestAccAzureRMImage_requiresImport(t *testing.T) { + ri := acctest.RandInt() + resourceGroup := fmt.Sprintf("acctestRG-%d", ri) + userName := "testadmin" + password := "Password1234!" + hostName := fmt.Sprintf("tftestcustomimagesrc%d", ri) + sshPort := "22" + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMImageDestroy, + Steps: []resource.TestStep{ + { + //need to create a vm and then reference it in the image creation + Config: testAccAzureRMImage_standaloneImage_setup(ri, userName, password, hostName, location), + Destroy: false, + Check: resource.ComposeTestCheckFunc( + testCheckAzureVMExists("azurerm_virtual_machine.testsource", true), + testGeneralizeVMImage(resourceGroup, "testsource", userName, password, hostName, sshPort, location), + ), + }, + { + Config: testAccAzureRMImage_standaloneImage_provision(ri, userName, password, hostName, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMImageExists("azurerm_image.test", true), + ), + }, + { + Config: testAccAzureRMImage_requiresImport(ri, userName, password, hostName, location), + ExpectError: testRequiresImportError("azurerm_image"), + }, + }, + }) +} + func TestAccAzureRMImage_customImageVMFromVHD(t *testing.T) { ri := acctest.RandInt() resourceGroup := fmt.Sprintf("acctestRG-%d", ri) @@ -573,6 +610,32 @@ resource "azurerm_image" "test" { `, rInt, location, rInt, rInt, rInt, hostName, rInt, rInt, userName, password) } +func testAccAzureRMImage_requiresImport(rInt int, userName string, password string, hostName string, location string) string { + template := testAccAzureRMImage_standaloneImage_provision(rInt, userName, password, hostName, location) + return fmt.Sprintf(` +%s + +resource "azurerm_image" "import" { + name = "${azurerm_image.test.name}" + location = "${azurerm_image.test.location}" + resource_group_name = "${azurerm_image.test.resource_group_name}" + + os_disk { + os_type = "Linux" + os_state = "Generalized" + blob_uri = "${azurerm_storage_account.test.primary_blob_endpoint}${azurerm_storage_container.test.name}/myosdisk1.vhd" + size_gb = 30 + caching = "None" + } + + tags { + environment = "Dev" + cost-center = "Ops" + } +} +`, template) +} + func testAccAzureRMImage_customImage_fromVHD_setup(rInt int, userName string, password string, hostName string, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_iothub.go b/azurerm/resource_arm_iothub.go index 6dc390482a94..e764a83793c1 100755 --- a/azurerm/resource_arm_iothub.go +++ b/azurerm/resource_arm_iothub.go @@ -13,19 +13,25 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" "strings" ) func resourceArmIotHub() *schema.Resource { return &schema.Resource{ - Create: resourceArmIotHubCreateAndUpdate, + Create: resourceArmIotHubCreateUpdate, Read: resourceArmIotHubRead, - Update: resourceArmIotHubCreateAndUpdate, + Update: resourceArmIotHubCreateUpdate, Delete: resourceArmIotHubDelete, 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": { @@ -249,25 +255,40 @@ func resourceArmIotHub() *schema.Resource { } -func resourceArmIotHubCreateAndUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceArmIotHubCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).iothubResourceClient ctx := meta.(*ArmClient).StopContext subscriptionID := meta.(*ArmClient).subscriptionId name := d.Get("name").(string) resourceGroup := d.Get("resource_group_name").(string) - res, err := client.CheckNameAvailability(ctx, devices.OperationInputs{ - Name: &name, - }) - if err != nil { - return fmt.Errorf("An error occurred checking if the IoTHub name was unique: %+v", err) - } + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of IoTHub %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_iothub", *resp.ID) + } + + res, err := client.CheckNameAvailability(ctx, devices.OperationInputs{ + Name: &name, + }) - if !*res.NameAvailable { - _, err := client.Get(ctx, resourceGroup, name) if err != nil { - return fmt.Errorf("An IoTHub already exists with the name %q - please choose an alternate name: %s", name, string(res.Reason)) + return fmt.Errorf("An error occurred checking if the IoTHub name was unique: %+v", err) + } + + if !*res.NameAvailable { + _, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("An IoTHub already exists with the name %q - please choose an alternate name: %s", name, string(res.Reason)) + } } } @@ -304,7 +325,9 @@ func resourceArmIotHubCreateAndUpdate(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error creating/updating IotHub %q (Resource Group %q): %+v", name, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for the completion of the creating/updating of IotHub %q (Resource Group %q): %+v", name, resourceGroup, err) } @@ -424,17 +447,18 @@ func resourceArmIotHubDelete(d *schema.ResourceData, meta interface{}) error { return err } - return waitForIotHubToBeDeleted(ctx, client, resourceGroup, name) + timeout := d.Timeout(schema.TimeoutDelete) + return waitForIotHubToBeDeleted(ctx, client, resourceGroup, name, timeout) } -func waitForIotHubToBeDeleted(ctx context.Context, client devices.IotHubResourceClient, resourceGroup, name string) error { +func waitForIotHubToBeDeleted(ctx context.Context, client devices.IotHubResourceClient, resourceGroup, name string, timeout time.Duration) error { // we can't use the Waiter here since the API returns a 404 once it's deleted which is considered a polling status code.. log.Printf("[DEBUG] Waiting for IotHub (%q in Resource Group %q) to be deleted", name, resourceGroup) stateConf := &resource.StateChangeConf{ Pending: []string{"200"}, Target: []string{"404"}, - Refresh: iothubStateStatusCodeRefreshFunc(ctx, client, resourceGroup, name), - Timeout: 40 * time.Minute, + Refresh: iothubStateStatusCodeRefreshFunc(ctx, client, resourceGroup, name, timeout), + Timeout: timeout, } if _, err := stateConf.WaitForState(); err != nil { return fmt.Errorf("Error waiting for IotHub (%q in Resource Group %q) to be deleted: %+v", name, resourceGroup, err) @@ -443,9 +467,11 @@ func waitForIotHubToBeDeleted(ctx context.Context, client devices.IotHubResource return nil } -func iothubStateStatusCodeRefreshFunc(ctx context.Context, client devices.IotHubResourceClient, resourceGroup, name string) resource.StateRefreshFunc { +func iothubStateStatusCodeRefreshFunc(ctx context.Context, client devices.IotHubResourceClient, resourceGroup, name string, timeout time.Duration) resource.StateRefreshFunc { return func() (interface{}, string, error) { - res, err := client.Get(ctx, resourceGroup, name) + waitCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + res, err := client.Get(waitCtx, resourceGroup, name) log.Printf("Retrieving IoTHub %q (Resource Group %q) returned Status %d", resourceGroup, name, res.StatusCode) diff --git a/azurerm/resource_arm_iothub_test.go b/azurerm/resource_arm_iothub_test.go index 3116bd4494e4..4cde3c19e774 100644 --- a/azurerm/resource_arm_iothub_test.go +++ b/azurerm/resource_arm_iothub_test.go @@ -28,6 +28,29 @@ func TestAccAzureRMIotHub_basic(t *testing.T) { }) } +func TestAccAzureRMIotHub_requiresImport(t *testing.T) { + rInt := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMIotHubDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotHub_basic(rInt, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMIotHubExists("azurerm_iothub.test"), + ), + }, + { + Config: testAccAzureRMIotHub_requiresImport(rInt, location), + ExpectError: testRequiresImportError("azurerm_iothub"), + }, + }, + }) +} + func TestAccAzureRMIotHub_standard(t *testing.T) { rInt := acctest.RandInt() @@ -143,6 +166,28 @@ resource "azurerm_iothub" "test" { `, rInt, location, rInt) } +func testAccAzureRMIotHub_requiresImport(rInt int, location string) string { + template := testAccAzureRMIotHub_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_iothub" "import" { + name = "${azurerm_iothub.test.name}" + resource_group_name = "${azurerm_iothub.test.resource_group_name}" + location = "${azurerm_iothub.test.location}" + sku { + name = "B1" + tier = "Basic" + capacity = "1" + } + + tags { + "purpose" = "testing" + } +} +`, template) +} + func testAccAzureRMIotHub_standard(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "foo" { diff --git a/azurerm/resource_arm_key_vault.go b/azurerm/resource_arm_key_vault.go index fd1c5ee8ef84..7add26cb21fc 100644 --- a/azurerm/resource_arm_key_vault.go +++ b/azurerm/resource_arm_key_vault.go @@ -1,6 +1,7 @@ package azurerm import ( + "context" "fmt" "log" "net/http" @@ -13,6 +14,7 @@ import ( "github.com/hashicorp/terraform/helper/validation" "github.com/satori/go.uuid" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -32,6 +34,11 @@ func resourceArmKeyVault() *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), + }, MigrateState: resourceAzureRMKeyVaultMigrateState, SchemaVersion: 1, @@ -130,8 +137,23 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e log.Printf("[INFO] preparing arguments for Azure ARM KeyVault creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_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 KeyVault %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_key_vault", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) tenantUUID := uuid.FromStringOrNil(d.Get("tenant_id").(string)) enabledForDeployment := d.Get("enabled_for_deployment").(bool) enabledForDiskEncryption := d.Get("enabled_for_disk_encryption").(bool) @@ -162,7 +184,9 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e azureRMLockByName(name, keyVaultResourceName) defer azureRMUnlockByName(name, keyVaultResourceName) - _, err = client.CreateOrUpdate(ctx, resGroup, name, parameters) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err = client.CreateOrUpdate(waitCtx, resGroup, name, parameters) if err != nil { return fmt.Errorf("Error updating Key Vault %q (Resource Group %q): %+v", name, resGroup, err) } @@ -261,7 +285,9 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error { azureRMLockByName(name, keyVaultResourceName) defer azureRMUnlockByName(name, keyVaultResourceName) - _, err = client.Delete(ctx, resGroup, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + _, err = client.Delete(waitCtx, resGroup, name) return err } diff --git a/azurerm/resource_arm_key_vault_access_policy.go b/azurerm/resource_arm_key_vault_access_policy.go index 24c96612df59..0bfa6eae0d38 100644 --- a/azurerm/resource_arm_key_vault_access_policy.go +++ b/azurerm/resource_arm_key_vault_access_policy.go @@ -1,8 +1,10 @@ package azurerm import ( + "context" "fmt" "log" + "time" "strings" @@ -10,6 +12,7 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/satori/go.uuid" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -22,6 +25,11 @@ func resourceArmKeyVaultAccessPolicy() *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{ "vault_name": { @@ -62,7 +70,7 @@ func resourceArmKeyVaultAccessPolicy() *schema.Resource { } } -func resourceArmKeyVaultAccessPolicyCreateOrDelete(d *schema.ResourceData, meta interface{}, action keyvault.AccessPolicyUpdateKind) error { +func resourceArmKeyVaultAccessPolicyCreateOrDelete(d *schema.ResourceData, meta interface{}, action keyvault.AccessPolicyUpdateKind, timeoutKey string) error { client := meta.(*ArmClient).keyVaultClient ctx := meta.(*ArmClient).StopContext log.Printf("[INFO] Preparing arguments for Key Vault Access Policy: %s.", action) @@ -101,12 +109,34 @@ func resourceArmKeyVaultAccessPolicyCreateOrDelete(d *schema.ResourceData, meta if applicationIdRaw != "" { applicationId, err := uuid.FromString(applicationIdRaw) if err != nil { - return fmt.Errorf("Error parsing Appliciation ID %q as a UUID: %+v", applicationIdRaw, err) + return fmt.Errorf("Error parsing Application ID %q as a UUID: %+v", applicationIdRaw, err) } accessPolicy.ApplicationID = &applicationId } + if d.IsNewResource() { + resp, err := client.Get(ctx, resGroup, vaultName) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for existence of Azure KeyVault %q (Resource Group %q): %+v", vaultName, resGroup, err) + } + } + + policy, err := findKeyVaultAccessPolicy(resp.Properties.AccessPolicies, objectId, applicationIdRaw) + if err != nil { + return fmt.Errorf("Error locating existing Access Policy (Object ID %q / Application ID %q) in Key Vault %q (Resource Group %q)", objectId, applicationIdRaw, vaultName, resGroup) + } + + if policy != nil { + resourceId := fmt.Sprintf("%s/objectId/%s", *resp.ID, objectId) + if applicationIdRaw != "" { + resourceId = fmt.Sprintf("%s/applicationId/%s", resourceId, applicationIdRaw) + } + return tf.ImportAsExistsError("azurerm_key_vault_access_policy", resourceId) + } + } + accessPolicies := []keyvault.AccessPolicyEntry{accessPolicy} parameters := keyvault.VaultAccessPolicyParameters{ @@ -120,7 +150,9 @@ func resourceArmKeyVaultAccessPolicyCreateOrDelete(d *schema.ResourceData, meta azureRMLockByName(vaultName, keyVaultResourceName) defer azureRMUnlockByName(vaultName, keyVaultResourceName) - _, err = client.UpdateAccessPolicy(ctx, resGroup, vaultName, action, parameters) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(timeoutKey)) + defer cancel() + _, err = client.UpdateAccessPolicy(waitCtx, resGroup, vaultName, action, parameters) if err != nil { return fmt.Errorf("Error updating Access Policy (Object ID %q / Application ID %q) for Key Vault %q (Resource Group %q): %+v", objectId, applicationIdRaw, vaultName, resGroup, err) } @@ -149,15 +181,15 @@ func resourceArmKeyVaultAccessPolicyCreateOrDelete(d *schema.ResourceData, meta } func resourceArmKeyVaultAccessPolicyCreate(d *schema.ResourceData, meta interface{}) error { - return resourceArmKeyVaultAccessPolicyCreateOrDelete(d, meta, keyvault.Add) + return resourceArmKeyVaultAccessPolicyCreateOrDelete(d, meta, keyvault.Add, schema.TimeoutCreate) } func resourceArmKeyVaultAccessPolicyDelete(d *schema.ResourceData, meta interface{}) error { - return resourceArmKeyVaultAccessPolicyCreateOrDelete(d, meta, keyvault.Remove) + return resourceArmKeyVaultAccessPolicyCreateOrDelete(d, meta, keyvault.Remove, schema.TimeoutDelete) } func resourceArmKeyVaultAccessPolicyUpdate(d *schema.ResourceData, meta interface{}) error { - return resourceArmKeyVaultAccessPolicyCreateOrDelete(d, meta, keyvault.Replace) + return resourceArmKeyVaultAccessPolicyCreateOrDelete(d, meta, keyvault.Replace, schema.TimeoutUpdate) } func resourceArmKeyVaultAccessPolicyRead(d *schema.ResourceData, meta interface{}) error { diff --git a/azurerm/resource_arm_key_vault_access_policy_test.go b/azurerm/resource_arm_key_vault_access_policy_test.go index d9b1976ba4b1..0a9569775027 100644 --- a/azurerm/resource_arm_key_vault_access_policy_test.go +++ b/azurerm/resource_arm_key_vault_access_policy_test.go @@ -33,6 +33,30 @@ func TestAccAzureRMKeyVaultAccessPolicy_basic(t *testing.T) { }) } +func TestAccAzureRMKeyVaultAccessPolicy_requiresImport(t *testing.T) { + resourceName := "azurerm_key_vault_access_policy.test" + rs := acctest.RandString(6) + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMKeyVaultDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMKeyVaultAccessPolicy_basic(rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultAccessPolicyExists(resourceName), + ), + }, + { + Config: testAccAzureRMKeyVaultAccessPolicy_requiresImport(rs, location), + ExpectError: testRequiresImportError("azurerm_key_vault_access_policy"), + }, + }, + }) +} + func TestAccAzureRMKeyVaultAccessPolicy_multiple(t *testing.T) { resourceName1 := "azurerm_key_vault_access_policy.test_with_application_id" resourceName2 := "azurerm_key_vault_access_policy.test_no_application_id" @@ -158,6 +182,30 @@ resource "azurerm_key_vault_access_policy" "test" { `, template) } +func testAccAzureRMKeyVaultAccessPolicy_requiresImport(rString string, location string) string { + template := testAccAzureRMKeyVaultAccessPolicy_basic(rString, location) + return fmt.Sprintf(` +%s + +resource "azurerm_key_vault_access_policy" "import" { + vault_name = "${azurerm_key_vault_access_policy.test.vault_name}" + resource_group_name = "${azurerm_key_vault_access_policy.test.resource_group_name}" + + key_permissions = [ + "get", + ] + + secret_permissions = [ + "get", + "set", + ] + + tenant_id = "${data.azurerm_client_config.current.tenant_id}" + object_id = "${data.azurerm_client_config.current.service_principal_object_id}" +} +`, template) +} + func testAccAzureRMKeyVaultAccessPolicy_multiple(rString string, location string) string { template := testAccAzureRMKeyVaultAccessPolicy_template(rString, location) return fmt.Sprintf(` diff --git a/azurerm/resource_arm_key_vault_certificate.go b/azurerm/resource_arm_key_vault_certificate.go index c6fb7e407970..889d3969782a 100644 --- a/azurerm/resource_arm_key_vault_certificate.go +++ b/azurerm/resource_arm_key_vault_certificate.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" "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" ) @@ -25,6 +26,10 @@ func resourceArmKeyVaultCertificate() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 60), + Delete: schema.DefaultTimeout(time.Minute * 60), + }, Schema: map[string]*schema.Schema{ "name": { @@ -255,8 +260,20 @@ func resourceArmKeyVaultCertificateCreate(d *schema.ResourceData, meta interface name := d.Get("name").(string) keyVaultBaseUrl := d.Get("vault_uri").(string) - tags := d.Get("tags").(map[string]interface{}) + // first check if there's one in this subscription requiring import + resp, err := client.GetCertificate(ctx, keyVaultBaseUrl, name, "") + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Key Vault Certificate %q (Key Vault %q): %+v", name, keyVaultBaseUrl, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_key_vault_certificate", *resp.ID) + } + + tags := d.Get("tags").(map[string]interface{}) policy := expandKeyVaultCertificatePolicy(d) if v, ok := d.GetOk("certificate"); ok { @@ -268,7 +285,9 @@ func resourceArmKeyVaultCertificateCreate(d *schema.ResourceData, meta interface CertificatePolicy: &policy, Tags: expandTags(tags), } - _, err := client.ImportCertificate(ctx, keyVaultBaseUrl, name, importParameters) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + _, err := client.ImportCertificate(waitCtx, keyVaultBaseUrl, name, importParameters) if err != nil { return err } @@ -284,11 +303,12 @@ func resourceArmKeyVaultCertificateCreate(d *schema.ResourceData, meta interface } log.Printf("[DEBUG] Waiting for Key Vault Certificate %q in Vault %q to be provisioned", name, keyVaultBaseUrl) + stateConf := &resource.StateChangeConf{ Pending: []string{"Provisioning"}, Target: []string{"Ready"}, Refresh: keyVaultCertificateCreationRefreshFunc(ctx, client, keyVaultBaseUrl, name), - Timeout: 60 * time.Minute, + Timeout: d.Timeout(schema.TimeoutCreate), MinTimeout: 15 * time.Second, } if _, err := stateConf.WaitForState(); err != nil { @@ -296,7 +316,7 @@ func resourceArmKeyVaultCertificateCreate(d *schema.ResourceData, meta interface } } - resp, err := client.GetCertificate(ctx, keyVaultBaseUrl, name, "") + resp, err = client.GetCertificate(ctx, keyVaultBaseUrl, name, "") if err != nil { return err } @@ -380,7 +400,9 @@ func resourceArmKeyVaultCertificateDelete(d *schema.ResourceData, meta interface return err } - resp, err := client.DeleteCertificate(ctx, id.KeyVaultBaseUrl, id.Name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.DeleteCertificate(waitCtx, id.KeyVaultBaseUrl, id.Name) if err != nil { if utils.ResponseWasNotFound(resp.Response) { return nil diff --git a/azurerm/resource_arm_key_vault_certificate_test.go b/azurerm/resource_arm_key_vault_certificate_test.go index 86cf2954d9e1..65ba8afdb9a5 100644 --- a/azurerm/resource_arm_key_vault_certificate_test.go +++ b/azurerm/resource_arm_key_vault_certificate_test.go @@ -31,6 +31,30 @@ func TestAccAzureRMKeyVaultCertificate_basicImportPFX(t *testing.T) { }) } +func TestAccAzureRMKeyVaultCertificate_requiresImport(t *testing.T) { + resourceName := "azurerm_key_vault_certificate.test" + rs := acctest.RandString(6) + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMKeyVaultCertificateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMKeyVaultCertificate_basicImportPFX(rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultCertificateExists(resourceName), + ), + }, + { + Config: testAccAzureRMKeyVaultCertificate_requiresImport(rs, location), + ExpectError: testRequiresImportError("azurerm_key_vault_certificate"), + }, + }, + }) +} + func TestAccAzureRMKeyVaultCertificate_disappears(t *testing.T) { resourceName := "azurerm_key_vault_certificate.test" rs := acctest.RandString(6) @@ -266,6 +290,40 @@ resource "azurerm_key_vault_certificate" "test" { `, rString, location, rString, rString) } +func testAccAzureRMKeyVaultCertificate_requiresImport(rString string, location string) string { + template := testAccAzureRMKeyVaultCertificate_basicImportPFX(rString, location) + return fmt.Sprintf(` +%s + +resource "azurerm_key_vault_certificate" "import" { + name = "${azurerm_key_vault_certificate.test.name}" + vault_uri = "${azurerm_key_vault_certificate.test.vault_uri}" + + certificate { + contents = "${base64encode(file("testdata/keyvaultcert.pfx"))}" + password = "" + } + + certificate_policy { + issuer_parameters { + name = "Self" + } + + key_properties { + exportable = true + key_size = 2048 + key_type = "RSA" + reuse_key = false + } + + secret_properties { + content_type = "application/x-pkcs12" + } + } +} +`, template) +} + func testAccAzureRMKeyVaultCertificate_basicGenerate(rString string, location string) string { return fmt.Sprintf(` data "azurerm_client_config" "current" {} diff --git a/azurerm/resource_arm_key_vault_key.go b/azurerm/resource_arm_key_vault_key.go index 7f7beeb82970..c79a5c5bc4b1 100644 --- a/azurerm/resource_arm_key_vault_key.go +++ b/azurerm/resource_arm_key_vault_key.go @@ -1,12 +1,15 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault" "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" ) @@ -19,6 +22,11 @@ func resourceArmKeyVaultKey() *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": { @@ -102,6 +110,18 @@ func resourceArmKeyVaultKeyCreate(d *schema.ResourceData, meta interface{}) erro name := d.Get("name").(string) keyVaultBaseUrl := d.Get("vault_uri").(string) + // first check if there's one in this subscription requiring import + resp, err := client.GetKey(ctx, keyVaultBaseUrl, name, "") + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Key Vault Key %q (Key Vault %q): %+v", name, keyVaultBaseUrl, err) + } + } + + if resp.Key != nil && resp.Key.Kid != nil { + return tf.ImportAsExistsError("azurerm_key_vault_key", *resp.Key.Kid) + } + keyType := d.Get("key_type").(string) keyOptions := expandKeyVaultKeyOptions(d) tags := d.Get("tags").(map[string]interface{}) @@ -118,7 +138,9 @@ func resourceArmKeyVaultKeyCreate(d *schema.ResourceData, meta interface{}) erro Tags: expandTags(tags), } - _, err := client.CreateKey(ctx, keyVaultBaseUrl, name, parameters) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + _, err = client.CreateKey(waitCtx, keyVaultBaseUrl, name, parameters) if err != nil { return fmt.Errorf("Error Creating Key: %+v", err) } @@ -155,7 +177,9 @@ func resourceArmKeyVaultKeyUpdate(d *schema.ResourceData, meta interface{}) erro Tags: expandTags(tags), } - _, err = client.UpdateKey(ctx, id.KeyVaultBaseUrl, id.Name, id.Version, parameters) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutUpdate)) + defer cancel() + _, err = client.UpdateKey(waitCtx, id.KeyVaultBaseUrl, id.Name, id.Version, parameters) if err != nil { return err } @@ -214,7 +238,9 @@ func resourceArmKeyVaultKeyDelete(d *schema.ResourceData, meta interface{}) erro return err } - _, err = client.DeleteKey(ctx, id.KeyVaultBaseUrl, id.Name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + _, err = client.DeleteKey(waitCtx, id.KeyVaultBaseUrl, id.Name) return err } diff --git a/azurerm/resource_arm_key_vault_key_test.go b/azurerm/resource_arm_key_vault_key_test.go index 7b4be7cfbc7b..dc4212cff3aa 100644 --- a/azurerm/resource_arm_key_vault_key_test.go +++ b/azurerm/resource_arm_key_vault_key_test.go @@ -30,6 +30,30 @@ func TestAccAzureRMKeyVaultKey_basicEC(t *testing.T) { }) } +func TestAccAzureRMKeyVaultKey_requiresImport(t *testing.T) { + resourceName := "azurerm_key_vault_key.test" + rs := acctest.RandString(6) + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMKeyVaultKeyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMKeyVaultKey_basicEC(rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultKeyExists(resourceName), + ), + }, + { + Config: testAccAzureRMKeyVaultKey_requiresImport(rs, location), + ExpectError: testRequiresImportError("azurerm_key_vault_key"), + }, + }, + }) +} + func TestAccAzureRMKeyVaultKey_basicRSA(t *testing.T) { resourceName := "azurerm_key_vault_key.test" rs := acctest.RandString(6) @@ -301,6 +325,25 @@ resource "azurerm_key_vault_key" "test" { `, rString, location, rString, rString) } +func testAccAzureRMKeyVaultKey_requiresImport(rString string, location string) string { + template := testAccAzureRMKeyVaultKey_basicEC(rString, location) + return fmt.Sprintf(` +%s + +resource "azurerm_key_vault_key" "import" { + name = "${azurerm_key_vault_key.test.name}" + vault_uri = "${azurerm_key_vault_key.test.vault_uri}" + key_type = "EC" + key_size = 2048 + + key_opts = [ + "sign", + "verify", + ] +} +`, template) +} + func testAccAzureRMKeyVaultKey_basicRSA(rString string, location string) string { return fmt.Sprintf(` data "azurerm_client_config" "current" {} diff --git a/azurerm/resource_arm_key_vault_secret.go b/azurerm/resource_arm_key_vault_secret.go index 91e3b283216c..a4e3456c2ec8 100644 --- a/azurerm/resource_arm_key_vault_secret.go +++ b/azurerm/resource_arm_key_vault_secret.go @@ -1,11 +1,14 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault" "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" ) @@ -18,6 +21,11 @@ func resourceArmKeyVaultSecret() *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": { @@ -62,6 +70,19 @@ func resourceArmKeyVaultSecretCreate(d *schema.ResourceData, meta interface{}) e name := d.Get("name").(string) keyVaultBaseUrl := d.Get("vault_uri").(string) + + // first check if there's one in this subscription requiring import + resp, err := client.GetSecret(ctx, keyVaultBaseUrl, name, "") + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of KeyVault Secret %q (KeyVault %q): %+v", name, keyVaultBaseUrl, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_key_vault_secret", *resp.ID) + } + value := d.Get("value").(string) contentType := d.Get("content_type").(string) tags := d.Get("tags").(map[string]interface{}) @@ -72,7 +93,9 @@ func resourceArmKeyVaultSecretCreate(d *schema.ResourceData, meta interface{}) e Tags: expandTags(tags), } - _, err := client.SetSecret(ctx, keyVaultBaseUrl, name, parameters) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + _, err = client.SetSecret(waitCtx, keyVaultBaseUrl, name, parameters) if err != nil { return err } @@ -113,7 +136,9 @@ func resourceArmKeyVaultSecretUpdate(d *schema.ResourceData, meta interface{}) e Tags: expandTags(tags), } - _, err := client.SetSecret(ctx, id.KeyVaultBaseUrl, id.Name, parameters) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutUpdate)) + defer cancel() + _, err := client.SetSecret(waitCtx, id.KeyVaultBaseUrl, id.Name, parameters) if err != nil { return err } @@ -132,8 +157,9 @@ func resourceArmKeyVaultSecretUpdate(d *schema.ResourceData, meta interface{}) e ContentType: utils.String(contentType), Tags: expandTags(tags), } - - _, err = client.UpdateSecret(ctx, id.KeyVaultBaseUrl, id.Name, id.Version, parameters) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutUpdate)) + defer cancel() + _, err = client.UpdateSecret(waitCtx, id.KeyVaultBaseUrl, id.Name, id.Version, parameters) if err != nil { return err } @@ -187,7 +213,9 @@ func resourceArmKeyVaultSecretDelete(d *schema.ResourceData, meta interface{}) e return err } - _, err = client.DeleteSecret(ctx, id.KeyVaultBaseUrl, id.Name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + _, err = client.DeleteSecret(waitCtx, id.KeyVaultBaseUrl, id.Name) return err } diff --git a/azurerm/resource_arm_key_vault_secret_test.go b/azurerm/resource_arm_key_vault_secret_test.go index 5b58b4aadc9d..1cf5843269c5 100644 --- a/azurerm/resource_arm_key_vault_secret_test.go +++ b/azurerm/resource_arm_key_vault_secret_test.go @@ -31,6 +31,30 @@ func TestAccAzureRMKeyVaultSecret_basic(t *testing.T) { }) } +func TestAccAzureRMKeyVaultSecret_requiresImport(t *testing.T) { + resourceName := "azurerm_key_vault_secret.test" + rs := acctest.RandString(6) + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMKeyVaultSecretDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMKeyVaultSecret_basic(rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultSecretExists(resourceName), + ), + }, + { + Config: testAccAzureRMKeyVaultSecret_requiresImport(rs, location), + ExpectError: testRequiresImportError("azurerm_key_vault_secret"), + }, + }, + }) +} + func TestAccAzureRMKeyVaultSecret_disappears(t *testing.T) { resourceName := "azurerm_key_vault_secret.test" rs := acctest.RandString(6) @@ -251,6 +275,19 @@ resource "azurerm_key_vault_secret" "test" { `, rString, location, rString, rString) } +func testAccAzureRMKeyVaultSecret_requiresImport(rString string, location string) string { + template := testAccAzureRMKeyVaultSecret_basic(rString, location) + return fmt.Sprintf(` +%s + +resource "azurerm_key_vault_secret" "import" { + name = "${azurerm_key_vault_secret.test.name}" + value = "${azurerm_key_vault_secret.test.value}" + vault_uri = "${azurerm_key_vault_secret.test.vault_uri}" +} +`, template) +} + func testAccAzureRMKeyVaultSecret_complete(rString string, location string) string { return fmt.Sprintf(` data "azurerm_client_config" "current" {} diff --git a/azurerm/resource_arm_key_vault_test.go b/azurerm/resource_arm_key_vault_test.go index c8d589e1cc8f..fd6d1b51dd7c 100644 --- a/azurerm/resource_arm_key_vault_test.go +++ b/azurerm/resource_arm_key_vault_test.go @@ -88,6 +88,29 @@ func TestAccAzureRMKeyVault_basic(t *testing.T) { }) } +func TestAccAzureRMKeyVault_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMKeyVaultDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMKeyVault_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultExists("azurerm_key_vault.test"), + ), + }, + { + Config: testAccAzureRMKeyVault_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_key_vault"), + }, + }, + }) +} + func TestAccAzureRMKeyVault_disappears(t *testing.T) { resourceName := "azurerm_key_vault.test" ri := acctest.RandInt() @@ -287,6 +310,37 @@ resource "azurerm_key_vault" "test" { `, rInt, location, rInt) } +func testAccAzureRMKeyVault_requiresImport(rInt int, location string) string { + template := testAccAzureRMKeyVault_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_key_vault" "import" { + name = "${azurerm_key_vault.test.name}" + location = "${azurerm_key_vault.test.location}" + resource_group_name = "${azurerm_key_vault.test.resource_group_name}" + tenant_id = "${azurerm_key_vault.test.tenant_id}" + + sku { + name = "premium" + } + + access_policy { + tenant_id = "${data.azurerm_client_config.current.tenant_id}" + object_id = "${data.azurerm_client_config.current.client_id}" + + key_permissions = [ + "create", + ] + + secret_permissions = [ + "set", + ] + } +} +`, template) +} + func testAccAzureRMKeyVault_update(rInt int, location string) string { return fmt.Sprintf(` data "azurerm_client_config" "current" {} diff --git a/azurerm/resource_arm_kubernetes_cluster.go b/azurerm/resource_arm_kubernetes_cluster.go index e0043f26c659..1b3a618fc409 100644 --- a/azurerm/resource_arm_kubernetes_cluster.go +++ b/azurerm/resource_arm_kubernetes_cluster.go @@ -2,27 +2,35 @@ package azurerm import ( "bytes" + "context" "fmt" "log" "regexp" + "time" "github.com/Azure/azure-sdk-for-go/services/containerservice/mgmt/2018-03-31/containerservice" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/kubernetes" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) func resourceArmKubernetesCluster() *schema.Resource { return &schema.Resource{ - Create: resourceArmKubernetesClusterCreate, + Create: resourceArmKubernetesClusterCreateUpdate, Read: resourceArmKubernetesClusterRead, - Update: resourceArmKubernetesClusterCreate, + Update: resourceArmKubernetesClusterCreateUpdate, Delete: resourceArmKubernetesClusterDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 60), + Update: schema.DefaultTimeout(time.Minute * 60), + Delete: schema.DefaultTimeout(time.Minute * 60), + }, CustomizeDiff: func(diff *schema.ResourceDiff, v interface{}) error { if v, exists := diff.GetOk("network_profile"); exists { @@ -365,14 +373,29 @@ func resourceArmKubernetesCluster() *schema.Resource { } } -func resourceArmKubernetesClusterCreate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*ArmClient) - kubernetesClustersClient := client.kubernetesClustersClient +func resourceArmKubernetesClusterCreateUpdate(d *schema.ResourceData, meta interface{}) error { + ctx := meta.(*ArmClient).StopContext + client := meta.(*ArmClient).kubernetesClustersClient log.Printf("[INFO] preparing arguments for Azure ARM AKS managed cluster creation.") - resGroup := d.Get("resource_group_name").(string) name := d.Get("name").(string) + resGroup := d.Get("resource_group_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 Kubernetes Cluster %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_kubernetes_cluster", *resp.ID) + } + } + location := azureRMNormalizeLocation(d.Get("location").(string)) dnsPrefix := d.Get("dns_prefix").(string) kubernetesVersion := d.Get("kubernetes_version").(string) @@ -412,18 +435,19 @@ func resourceArmKubernetesClusterCreate(d *schema.ResourceData, meta interface{} Tags: expandTags(tags), } - ctx := client.StopContext - future, err := kubernetesClustersClient.CreateOrUpdate(ctx, resGroup, name, parameters) + future, err := client.CreateOrUpdate(ctx, resGroup, name, parameters) if err != nil { return err } - err = future.WaitForCompletionRef(ctx, kubernetesClustersClient.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } - read, err := kubernetesClustersClient.Get(ctx, resGroup, name) + read, err := client.Get(ctx, resGroup, name) if err != nil { return err } @@ -515,8 +539,8 @@ func resourceArmKubernetesClusterRead(d *schema.ResourceData, meta interface{}) } func resourceArmKubernetesClusterDelete(d *schema.ResourceData, meta interface{}) error { - client := meta.(*ArmClient) - kubernetesClustersClient := client.kubernetesClustersClient + ctx := meta.(*ArmClient).StopContext + client := meta.(*ArmClient).kubernetesClustersClient id, err := parseAzureResourceID(d.Id()) if err != nil { @@ -525,13 +549,14 @@ func resourceArmKubernetesClusterDelete(d *schema.ResourceData, meta interface{} resGroup := id.ResourceGroup name := id.Path["managedClusters"] - ctx := client.StopContext - future, err := kubernetesClustersClient.Delete(ctx, resGroup, name) + future, err := client.Delete(ctx, resGroup, name) if err != nil { return fmt.Errorf("Error issuing AzureRM delete request of AKS Managed Cluster %q (resource Group %q): %+v", name, resGroup, err) } - return future.WaitForCompletionRef(ctx, kubernetesClustersClient.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + return future.WaitForCompletionRef(waitCtx, client.Client) } func flattenAzureRmKubernetesClusterLinuxProfile(profile *containerservice.LinuxProfile) []interface{} { diff --git a/azurerm/resource_arm_kubernetes_cluster_test.go b/azurerm/resource_arm_kubernetes_cluster_test.go index 86fde1c0837a..7a8f32d4de0a 100644 --- a/azurerm/resource_arm_kubernetes_cluster_test.go +++ b/azurerm/resource_arm_kubernetes_cluster_test.go @@ -98,6 +98,32 @@ func TestAccAzureRMKubernetesCluster_basic(t *testing.T) { }) } +func TestAccAzureRMKubernetesCluster_requiresImport(t *testing.T) { + resourceName := "azurerm_kubernetes_cluster.test" + ri := acctest.RandInt() + location := testLocation() + clientId := os.Getenv("ARM_CLIENT_ID") + clientSecret := os.Getenv("ARM_CLIENT_SECRET") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMKubernetesClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMKubernetesCluster_basic(ri, clientId, clientSecret, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKubernetesClusterExists(resourceName), + ), + }, + { + Config: testAccAzureRMKubernetesCluster_requiresImport(ri, clientId, clientSecret, location), + ExpectError: testRequiresImportError("azurerm_kubernetes_cluster"), + }, + }, + }) +} + func TestAccAzureRMKubernetesCluster_linuxProfile(t *testing.T) { resourceName := "azurerm_kubernetes_cluster.test" ri := acctest.RandInt() @@ -417,6 +443,40 @@ resource "azurerm_kubernetes_cluster" "test" { `, rInt, location, rInt, rInt, rInt, clientId, clientSecret) } +func testAccAzureRMKubernetesCluster_requiresImport(rInt int, clientId string, clientSecret string, location string) string { + template := testAccAzureRMKubernetesCluster_basic(rInt, clientId, clientSecret, location) + return fmt.Sprintf(` +%s + +resource "azurerm_kubernetes_cluster" "import" { + name = "${azurerm_kubernetes_cluster.test.name}" + location = "${azurerm_kubernetes_cluster.test.location}" + resource_group_name = "${azurerm_kubernetes_cluster.test.resource_group_name}" + dns_prefix = "${azurerm_kubernetes_cluster.test.dns_prefix}" + kubernetes_version = "1.7.7" + + linux_profile { + admin_username = "acctestuser%d" + + ssh_key { + key_data = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCqaZoyiz1qbdOQ8xEf6uEu1cCwYowo5FHtsBhqLoDnnp7KUTEBN+L2NxRIfQ781rxV6Iq5jSav6b2Q8z5KiseOlvKA/RF2wqU0UPYqQviQhLmW6THTpmrv/YkUCuzxDpsH7DUDhZcwySLKVVe0Qm3+5N2Ta6UYH3lsDf9R9wTP2K/+vAnflKebuypNlmocIvakFWoZda18FOmsOoIVXQ8HWFNCuw9ZCunMSN62QGamCe3dL5cXlkgHYv7ekJE15IA9aOJcM7e90oeTqo+7HTcWfdu0qQqPWY5ujyMw/llas8tsXY85LFqRnr3gJ02bAscjc477+X+j/gkpFoN1QEmt terraform@demo.tld" + } + } + + agent_pool_profile { + name = "default" + count = "1" + vm_size = "Standard_DS2_v2" + } + + service_principal { + client_id = "%s" + client_secret = "%s" + } +} +`, template, rInt, clientId, clientSecret) +} + func testAccAzureRMKubernetesCluster_addAgent(rInt int, clientId string, clientSecret string, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_loadbalancer.go b/azurerm/resource_arm_loadbalancer.go index ce5f45e8e02d..ef5713f6b7df 100644 --- a/azurerm/resource_arm_loadbalancer.go +++ b/azurerm/resource_arm_loadbalancer.go @@ -1,30 +1,35 @@ package azurerm import ( + "context" "fmt" "log" "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" - "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/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/utils" ) func resourceArmLoadBalancer() *schema.Resource { return &schema.Resource{ - Create: resourceArmLoadBalancerCreate, + Create: resourceArmLoadBalancerCreateUpdate, Read: resourceArmLoadBalancerRead, - Update: resourceArmLoadBalancerCreate, + Update: resourceArmLoadBalancerCreateUpdate, Delete: resourceArmLoadBalancerDelete, - 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": { @@ -136,15 +141,29 @@ func resourceArmLoadBalancer() *schema.Resource { } } -func resourceArmLoadBalancerCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmLoadBalancerCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).loadBalancerClient ctx := meta.(*ArmClient).StopContext log.Printf("[INFO] preparing arguments for Azure ARM Load Balancer creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_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 Load Balancer %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_lb", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) sku := network.LoadBalancerSku{ Name: network.LoadBalancerSkuName(d.Get("sku").(string)), } @@ -170,7 +189,9 @@ func resourceArmLoadBalancerCreate(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("Error Creating/Updating Load Balancer %q (Resource Group %q): %+v", name, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error Creating/Updating Load Balancer %q (Resource Group %q): %+v", name, resGroup, err) } @@ -185,18 +206,6 @@ func resourceArmLoadBalancerCreate(d *schema.ResourceData, meta interface{}) err d.SetId(*read.ID) - // TODO: is this still needed? - log.Printf("[DEBUG] Waiting for Load Balancer (%q) to become available", name) - stateConf := &resource.StateChangeConf{ - Pending: []string{"Accepted", "Updating"}, - Target: []string{"Succeeded"}, - Refresh: loadbalancerStateRefreshFunc(ctx, client, resGroup, name), - Timeout: 10 * time.Minute, - } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf("Error waiting for Load Balancer (%q - Resource Group %q) to become available: %s", name, resGroup, err) - } - return resourceArmLoadBalancerRead(d, meta) } @@ -270,7 +279,9 @@ func resourceArmLoadBalancerDelete(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("Error deleting Load Balancer %q (Resource Group %q): %+v", name, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for the deleting Load Balancer %q (Resource Group %q): %+v", name, resGroup, err) } diff --git a/azurerm/resource_arm_loadbalancer_backend_address_pool.go b/azurerm/resource_arm_loadbalancer_backend_address_pool.go index cca71e22426a..8e3c20d5130a 100644 --- a/azurerm/resource_arm_loadbalancer_backend_address_pool.go +++ b/azurerm/resource_arm_loadbalancer_backend_address_pool.go @@ -1,15 +1,16 @@ package azurerm import ( + "context" "fmt" "log" "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" - "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -21,6 +22,10 @@ func resourceArmLoadBalancerBackendAddressPool() *schema.Resource { Importer: &schema.ResourceImporter{ State: loadBalancerSubResourceStateImporter, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Schema: map[string]*schema.Schema{ "name": { @@ -68,7 +73,13 @@ func resourceArmLoadBalancerBackendAddressPoolCreate(d *schema.ResourceData, met client := meta.(*ArmClient).loadBalancerClient ctx := meta.(*ArmClient).StopContext + name := d.Get("name").(string) loadBalancerID := d.Get("loadbalancer_id").(string) + resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(loadBalancerID) + if err != nil { + return fmt.Errorf("Error parsing Load Balancer Name and Group: %+v", err) + } + armMutexKV.Lock(loadBalancerID) defer armMutexKV.Unlock(loadBalancerID) @@ -78,33 +89,44 @@ func resourceArmLoadBalancerBackendAddressPoolCreate(d *schema.ResourceData, met } if !exists { d.SetId("") - log.Printf("[INFO] Load Balancer %q not found. Removing from state", d.Get("name").(string)) + log.Printf("[INFO] Load Balancer %q not found. Removing from state", name) return nil } - backendAddressPools := append(*loadBalancer.LoadBalancerPropertiesFormat.BackendAddressPools, expandAzureRmLoadBalancerBackendAddressPools(d)) - existingPool, existingPoolIndex, exists := findLoadBalancerBackEndAddressPoolByName(loadBalancer, d.Get("name").(string)) - if exists { - if d.Get("name").(string) == *existingPool.Name { - // this pool is being updated/reapplied remove old copy from the slice - backendAddressPools = append(backendAddressPools[:existingPoolIndex], backendAddressPools[existingPoolIndex+1:]...) + props := loadBalancer.LoadBalancerPropertiesFormat + if props == nil { + return fmt.Errorf("[ERROR] Load Balancer Properties was nil - unable to configure Backend Address Pool") + } + + pools := props.BackendAddressPools + if pools == nil { + return fmt.Errorf("[ERROR] Load Balancer Backend Address Pools was nil - unable to configure Backend Address Pool") + } + + // firstly does this already exist + for _, pool := range *pools { + if pool.Name != nil && *pool.Name == name { + return tf.ImportAsExistsError("azurerm_lb_backend_address_pool", *pool.ID) } } - loadBalancer.LoadBalancerPropertiesFormat.BackendAddressPools = &backendAddressPools - resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(d.Get("loadbalancer_id").(string)) - if err != nil { - return fmt.Errorf("Error parsing Load Balancer Name and Group: %+v", err) + // as such we should just be able to append and send + newPool := network.BackendAddressPool{ + Name: utils.String(name), } + backendAddressPools := append(*pools, newPool) + loadBalancer.LoadBalancerPropertiesFormat.BackendAddressPools = &backendAddressPools future, err := client.CreateOrUpdate(ctx, resGroup, loadBalancerName, *loadBalancer) if err != nil { - return fmt.Errorf("Error Creating/Updating Load Balancer %q (Resource Group %q): %+v", loadBalancerName, resGroup, err) + return fmt.Errorf("Error Updating Load Balancer %q (Resource Group %q): %+v", loadBalancerName, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { - return fmt.Errorf("Error Creating/Updating Load Balancer %q (Resource Group %q): %+v", loadBalancerName, resGroup, err) + return fmt.Errorf("Error Updating Load Balancer %q (Resource Group %q): %+v", loadBalancerName, resGroup, err) } read, err := client.Get(ctx, resGroup, loadBalancerName, "") @@ -116,9 +138,13 @@ func resourceArmLoadBalancerBackendAddressPoolCreate(d *schema.ResourceData, met } var poolId string - for _, BackendAddressPool := range *(*read.LoadBalancerPropertiesFormat).BackendAddressPools { - if *BackendAddressPool.Name == d.Get("name").(string) { - poolId = *BackendAddressPool.ID + if props := read.LoadBalancerPropertiesFormat; props != nil { + if pools := props.BackendAddressPools; pools != nil { + for _, pool := range *pools { + if pool.Name != nil && *pool.Name == name { + poolId = *pool.ID + } + } } } @@ -128,18 +154,6 @@ func resourceArmLoadBalancerBackendAddressPoolCreate(d *schema.ResourceData, met d.SetId(poolId) - // TODO: is this still needed? - log.Printf("[DEBUG] Waiting for Load Balancer (%s) to become available", loadBalancerName) - stateConf := &resource.StateChangeConf{ - Pending: []string{"Accepted", "Updating"}, - Target: []string{"Succeeded"}, - Refresh: loadbalancerStateRefreshFunc(ctx, client, resGroup, loadBalancerName), - Timeout: 10 * time.Minute, - } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf("Error waiting for Load Balancer (%q Resource Group %q) to become available: %+v", loadBalancerName, resGroup, err) - } - return resourceArmLoadBalancerBackendAddressPoolRead(d, meta) } @@ -149,8 +163,9 @@ func resourceArmLoadBalancerBackendAddressPoolRead(d *schema.ResourceData, meta return err } name := id.Path["backendAddressPools"] + loadBalancerId := d.Get("loadbalancer_id").(string) - loadBalancer, exists, err := retrieveLoadBalancerById(d.Get("loadbalancer_id").(string), meta) + loadBalancer, exists, err := retrieveLoadBalancerById(loadBalancerId, meta) if err != nil { return fmt.Errorf("Error retrieving Load Balancer by ID: %+v", err) } @@ -160,20 +175,31 @@ func resourceArmLoadBalancerBackendAddressPoolRead(d *schema.ResourceData, meta return nil } - config, _, exists := findLoadBalancerBackEndAddressPoolByName(loadBalancer, name) - if !exists { + var pool *network.BackendAddressPool + if props := loadBalancer.LoadBalancerPropertiesFormat; props != nil { + if pools := props.BackendAddressPools; pools != nil { + for _, p := range *pools { + if p.Name != nil && *p.Name == name { + pool = &p + break + } + } + } + } + + if pool == nil { d.SetId("") - log.Printf("[INFO] Load Balancer Backend Address Pool %q not found. Removing from state", name) + log.Printf("[INFO] Backend Address Pool %q was not found on Load Balancer %q. Removing from state", name, *loadBalancer.ID) return nil } - d.Set("name", config.Name) + d.Set("name", name) d.Set("resource_group_name", id.ResourceGroup) var backendIpConfigurations []string var loadBalancingRules []string - if props := config.BackendAddressPoolPropertiesFormat; props != nil { + if props := pool.BackendAddressPoolPropertiesFormat; props != nil { if configs := props.BackendIPConfigurations; configs != nil { for _, backendConfig := range *configs { backendIpConfigurations = append(backendIpConfigurations, *backendConfig.ID) @@ -197,6 +223,7 @@ func resourceArmLoadBalancerBackendAddressPoolDelete(d *schema.ResourceData, met client := meta.(*ArmClient).loadBalancerClient ctx := meta.(*ArmClient).StopContext + name := d.Get("name").(string) loadBalancerID := d.Get("loadbalancer_id").(string) armMutexKV.Lock(loadBalancerID) defer armMutexKV.Unlock(loadBalancerID) @@ -206,20 +233,29 @@ func resourceArmLoadBalancerBackendAddressPoolDelete(d *schema.ResourceData, met return fmt.Errorf("Error retrieving Load Balancer by ID: %+v", err) } if !exists { - d.SetId("") return nil } - _, index, exists := findLoadBalancerBackEndAddressPoolByName(loadBalancer, d.Get("name").(string)) - if !exists { + props := loadBalancer.LoadBalancerPropertiesFormat + if props == nil { return nil } - oldBackEndPools := *loadBalancer.LoadBalancerPropertiesFormat.BackendAddressPools - newBackEndPools := append(oldBackEndPools[:index], oldBackEndPools[index+1:]...) + pools := props.BackendAddressPools + if pools == nil { + return nil + } + + newBackEndPools := make([]network.BackendAddressPool, 0) + for _, pool := range *pools { + if pool.Name != nil && *pool.Name != name { + newBackEndPools = append(newBackEndPools, pool) + } + } + loadBalancer.LoadBalancerPropertiesFormat.BackendAddressPools = &newBackEndPools - resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(d.Get("loadbalancer_id").(string)) + resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(loadBalancerID) if err != nil { return fmt.Errorf("Error Getting Load Balancer Name and Group:: %+v", err) } @@ -229,7 +265,9 @@ func resourceArmLoadBalancerBackendAddressPoolDelete(d *schema.ResourceData, met return fmt.Errorf("Error Creating/Updating LoadBalancer: %+v", err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for the completion for the LoadBalancer: %+v", err) } @@ -244,9 +282,3 @@ func resourceArmLoadBalancerBackendAddressPoolDelete(d *schema.ResourceData, met return nil } - -func expandAzureRmLoadBalancerBackendAddressPools(d *schema.ResourceData) network.BackendAddressPool { - return network.BackendAddressPool{ - Name: utils.String(d.Get("name").(string)), - } -} diff --git a/azurerm/resource_arm_loadbalancer_backend_address_pool_test.go b/azurerm/resource_arm_loadbalancer_backend_address_pool_test.go index 99203223ecc7..3bb3058f7bc0 100644 --- a/azurerm/resource_arm_loadbalancer_backend_address_pool_test.go +++ b/azurerm/resource_arm_loadbalancer_backend_address_pool_test.go @@ -46,6 +46,31 @@ func TestAccAzureRMLoadBalancerBackEndAddressPool_basic(t *testing.T) { }) } +func TestAccAzureRMLoadBalancerBackEndAddressPool_requiresImport(t *testing.T) { + var lb network.LoadBalancer + ri := acctest.RandInt() + addressPoolName := fmt.Sprintf("%d-address-pool", ri) + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMLoadBalancerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMLoadBalancerBackEndAddressPool_basic(ri, addressPoolName, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMLoadBalancerExists("azurerm_lb.test", &lb), + ), + }, + { + Config: testAccAzureRMLoadBalancerBackEndAddressPool_requiresImport(ri, addressPoolName, location), + ExpectError: testRequiresImportError("azurerm_lb_backend_address_pool"), + }, + }, + }) +} + func TestAccAzureRMLoadBalancerBackEndAddressPool_removal(t *testing.T) { var lb network.LoadBalancer ri := acctest.RandInt() @@ -126,7 +151,7 @@ func TestAccAzureRMLoadBalancerBackEndAddressPool_disappears(t *testing.T) { func testCheckAzureRMLoadBalancerBackEndAddressPoolExists(addressPoolName string, lb *network.LoadBalancer) resource.TestCheckFunc { return func(s *terraform.State) error { - _, _, exists := findLoadBalancerBackEndAddressPoolByName(lb, addressPoolName) + _, exists := findAzureRMLoadBalancerBackEndAddressPoolByName(lb, addressPoolName) if !exists { return fmt.Errorf("A BackEnd Address Pool with name %q cannot be found.", addressPoolName) } @@ -137,7 +162,7 @@ func testCheckAzureRMLoadBalancerBackEndAddressPoolExists(addressPoolName string func testCheckAzureRMLoadBalancerBackEndAddressPoolNotExists(addressPoolName string, lb *network.LoadBalancer) resource.TestCheckFunc { return func(s *terraform.State) error { - _, _, exists := findLoadBalancerBackEndAddressPoolByName(lb, addressPoolName) + _, exists := findAzureRMLoadBalancerBackEndAddressPoolByName(lb, addressPoolName) if exists { return fmt.Errorf("A BackEnd Address Pool with name %q has been found.", addressPoolName) } @@ -151,7 +176,7 @@ func testCheckAzureRMLoadBalancerBackEndAddressPoolDisappears(addressPoolName st client := testAccProvider.Meta().(*ArmClient).loadBalancerClient ctx := testAccProvider.Meta().(*ArmClient).StopContext - _, i, exists := findLoadBalancerBackEndAddressPoolByName(lb, addressPoolName) + i, exists := findAzureRMLoadBalancerBackEndAddressPoolByName(lb, addressPoolName) if !exists { return fmt.Errorf("A BackEnd Address Pool with name %q cannot be found.", addressPoolName) } @@ -180,6 +205,20 @@ func testCheckAzureRMLoadBalancerBackEndAddressPoolDisappears(addressPoolName st } } +func findAzureRMLoadBalancerBackEndAddressPoolByName(lb *network.LoadBalancer, name string) (int, bool) { + if lb == nil || lb.LoadBalancerPropertiesFormat == nil || lb.LoadBalancerPropertiesFormat.BackendAddressPools == nil { + return -1, false + } + + for i, apc := range *lb.LoadBalancerPropertiesFormat.BackendAddressPools { + if apc.Name != nil && *apc.Name == name { + return i, true + } + } + + return -1, false +} + func testAccAzureRMLoadBalancerBackEndAddressPool_basic(rInt int, addressPoolName string, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { @@ -215,6 +254,20 @@ resource "azurerm_lb_backend_address_pool" "test" { `, rInt, location, rInt, rInt, rInt, addressPoolName) } +func testAccAzureRMLoadBalancerBackEndAddressPool_requiresImport(rInt int, addressPoolName string, location string) string { + template := testAccAzureRMLoadBalancerBackEndAddressPool_basic(rInt, addressPoolName, location) + return fmt.Sprintf(` +%s + +resource "azurerm_lb_backend_address_pool" "import" { + name = "${azurerm_lb_backend_address_pool.test.name}" + location = "${azurerm_lb_backend_address_pool.test.location}" + resource_group_name = "${azurerm_lb_backend_address_pool.test.resource_group_name}" + loadbalancer_id = "${azurerm_lb_backend_address_pool.test.loadbalancer_id}" +} +`, template) +} + func testAccAzureRMLoadBalancerBackEndAddressPool_removal(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_loadbalancer_nat_pool.go b/azurerm/resource_arm_loadbalancer_nat_pool.go index d8ffc22972d0..fe1b5b7ab180 100644 --- a/azurerm/resource_arm_loadbalancer_nat_pool.go +++ b/azurerm/resource_arm_loadbalancer_nat_pool.go @@ -1,16 +1,17 @@ package azurerm import ( + "context" "fmt" "log" "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" - "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/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/utils" ) @@ -24,6 +25,11 @@ func resourceArmLoadBalancerNatPool() *schema.Resource { Importer: &schema.ResourceImporter{ State: loadBalancerSubResourceStateImporter, }, + 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": { @@ -92,7 +98,9 @@ func resourceArmLoadBalancerNatPoolCreate(d *schema.ResourceData, meta interface client := meta.(*ArmClient).loadBalancerClient ctx := meta.(*ArmClient).StopContext + name := d.Get("name").(string) loadBalancerID := d.Get("loadbalancer_id").(string) + armMutexKV.Lock(loadBalancerID) defer armMutexKV.Unlock(loadBalancerID) @@ -100,34 +108,67 @@ func resourceArmLoadBalancerNatPoolCreate(d *schema.ResourceData, meta interface if err != nil { return fmt.Errorf("Error Getting Load Balancer By ID: %+v", err) } + if !exists { d.SetId("") - log.Printf("[INFO] Load Balancer %q not found. Removing from state", d.Get("name").(string)) + log.Printf("[INFO] Load Balancer %q not found. Removing from state", name) return nil } - newNatPool, err := expandAzureRmLoadBalancerNatPool(d, loadBalancer) + newPool, err := expandAzureRmLoadBalancerNatPool(d, loadBalancer) if err != nil { return fmt.Errorf("Error Expanding NAT Pool: %+v", err) } - natPools := append(*loadBalancer.LoadBalancerPropertiesFormat.InboundNatPools, *newNatPool) + props := loadBalancer.LoadBalancerPropertiesFormat + if props == nil { + return fmt.Errorf("Error updating Load Balancer NAT Pool: props was nil") + } + + pools := props.InboundNatPools + if pools == nil { + return fmt.Errorf("Error updating Load Balancer NAT Pool: props.InboundNatPools was nil") + } + inboundNatPools := *pools + + if d.IsNewResource() { + // if this exists, it should be imported into the state + for _, v := range inboundNatPools { + if v.Name != nil && *v.Name == name { + return tf.ImportAsExistsError("azurerm_lb_nat_pool", *v.ID) + } + } + + // if not, we can simply append it + inboundNatPools = append(inboundNatPools, *newPool) + loadBalancer.LoadBalancerPropertiesFormat.InboundNatPools = &inboundNatPools + } else { + // find the existing item in the state and switch it out + index := -1 + for i, v := range inboundNatPools { + if v.Name != nil && *v.Name == name { + index = i + break + } + } - existingNatPool, existingNatPoolIndex, exists := findLoadBalancerNatPoolByName(loadBalancer, d.Get("name").(string)) - if exists { - if d.Get("name").(string) == *existingNatPool.Name { - // this probe is being updated/reapplied remove old copy from the slice - natPools = append(natPools[:existingNatPoolIndex], natPools[existingNatPoolIndex+1:]...) + if index == -1 { + // note: this should be caught by the read + return fmt.Errorf("Error: Inbound Nat Pool %q was not found on Load Balancer %q", name, loadBalancerID) } + + inboundNatPools[index] = *newPool } - loadBalancer.LoadBalancerPropertiesFormat.InboundNatPools = &natPools - resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(d.Get("loadbalancer_id").(string)) + loadBalancer.LoadBalancerPropertiesFormat.InboundNatPools = &inboundNatPools + resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(loadBalancerID) if err != nil { - return fmt.Errorf("Error Getting Load Balancer Name and Group:: %+v", err) + return fmt.Errorf("Error Getting Load Balancer Name and Group: %+v", err) } - future, err := client.CreateOrUpdate(ctx, resGroup, loadBalancerName, *loadBalancer) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + future, err := client.CreateOrUpdate(waitCtx, resGroup, loadBalancerName, *loadBalancer) if err != nil { return fmt.Errorf("Error Creating/Updating Load Balancer %q (Resource Group %q): %+v", loadBalancerName, resGroup, err) } @@ -146,9 +187,13 @@ func resourceArmLoadBalancerNatPoolCreate(d *schema.ResourceData, meta interface } var natPoolId string - for _, InboundNatPool := range *(*read.LoadBalancerPropertiesFormat).InboundNatPools { - if *InboundNatPool.Name == d.Get("name").(string) { - natPoolId = *InboundNatPool.ID + if props := read.LoadBalancerPropertiesFormat; props != nil { + if pools := props.InboundNatPools; pools != nil { + for _, pool := range *pools { + if *pool.Name == name { + natPoolId = *pool.ID + } + } } } @@ -158,18 +203,6 @@ func resourceArmLoadBalancerNatPoolCreate(d *schema.ResourceData, meta interface d.SetId(natPoolId) - // TODO: is this needed? - log.Printf("[DEBUG] Waiting for Load Balancer (%q) to become available", loadBalancerName) - stateConf := &resource.StateChangeConf{ - Pending: []string{"Accepted", "Updating"}, - Target: []string{"Succeeded"}, - Refresh: loadbalancerStateRefreshFunc(ctx, client, resGroup, loadBalancerName), - Timeout: 10 * time.Minute, - } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf("Error waiting for Load Balancer (%q - Resource Group %q) to become available: %+v", loadBalancerName, resGroup, err) - } - return resourceArmLoadBalancerNatPoolRead(d, meta) } @@ -179,8 +212,9 @@ func resourceArmLoadBalancerNatPoolRead(d *schema.ResourceData, meta interface{} return err } name := id.Path["inboundNatPools"] + loadBalancerId := d.Get("loadbalancer_id").(string) - loadBalancer, exists, err := retrieveLoadBalancerById(d.Get("loadbalancer_id").(string), meta) + loadBalancer, exists, err := retrieveLoadBalancerById(loadBalancerId, meta) if err != nil { return fmt.Errorf("Error retrieving Load Balancer by ID: %+v", err) } @@ -190,17 +224,29 @@ func resourceArmLoadBalancerNatPoolRead(d *schema.ResourceData, meta interface{} return nil } - config, _, exists := findLoadBalancerNatPoolByName(loadBalancer, name) - if !exists { + var pool *network.InboundNatPool + + if props := loadBalancer.LoadBalancerPropertiesFormat; props != nil { + if pools := props.InboundNatPools; pools != nil { + for _, p := range *pools { + if p.Name != nil && *p.Name == name { + pool = &p + break + } + } + } + } + + if pool == nil { d.SetId("") log.Printf("[INFO] Load Balancer Nat Pool %q not found. Removing from state", name) return nil } - d.Set("name", config.Name) + d.Set("name", name) d.Set("resource_group_name", id.ResourceGroup) - if props := config.InboundNatPoolPropertiesFormat; props != nil { + if props := pool.InboundNatPoolPropertiesFormat; props != nil { d.Set("protocol", props.Protocol) d.Set("frontend_port_start", props.FrontendPortRangeStart) d.Set("frontend_port_end", props.FrontendPortRangeEnd) @@ -224,7 +270,9 @@ func resourceArmLoadBalancerNatPoolDelete(d *schema.ResourceData, meta interface client := meta.(*ArmClient).loadBalancerClient ctx := meta.(*ArmClient).StopContext + name := d.Get("name").(string) loadBalancerID := d.Get("loadbalancer_id").(string) + armMutexKV.Lock(loadBalancerID) defer armMutexKV.Unlock(loadBalancerID) @@ -237,16 +285,27 @@ func resourceArmLoadBalancerNatPoolDelete(d *schema.ResourceData, meta interface return nil } - _, index, exists := findLoadBalancerNatPoolByName(loadBalancer, d.Get("name").(string)) - if !exists { + props := loadBalancer.LoadBalancerPropertiesFormat + if props == nil { + return nil + } + + pools := props.InboundNatPools + if pools == nil { return nil } - oldNatPools := *loadBalancer.LoadBalancerPropertiesFormat.InboundNatPools - newNatPools := append(oldNatPools[:index], oldNatPools[index+1:]...) - loadBalancer.LoadBalancerPropertiesFormat.InboundNatPools = &newNatPools + inboundNatPools := make([]network.InboundNatPool, 0) + for _, pool := range *pools { + if pool.Name != nil && *pool.Name == name { + inboundNatPools = append(inboundNatPools, pool) + break + } + } - resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(d.Get("loadbalancer_id").(string)) + loadBalancer.LoadBalancerPropertiesFormat.InboundNatPools = &inboundNatPools + + resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(loadBalancerID) if err != nil { return fmt.Errorf("Error Getting Load Balancer Name and Group:: %+v", err) } @@ -256,7 +315,9 @@ func resourceArmLoadBalancerNatPoolDelete(d *schema.ResourceData, meta interface return fmt.Errorf("Error creating/updating Load Balancer %q (Resource Group %q): %+v", loadBalancerName, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for completion of the Load Balancer %q (Resource Group %q): %+v", loadBalancerName, resGroup, err) } diff --git a/azurerm/resource_arm_loadbalancer_nat_pool_test.go b/azurerm/resource_arm_loadbalancer_nat_pool_test.go index 1e26851f0146..4a22569dbf31 100644 --- a/azurerm/resource_arm_loadbalancer_nat_pool_test.go +++ b/azurerm/resource_arm_loadbalancer_nat_pool_test.go @@ -46,6 +46,31 @@ func TestAccAzureRMLoadBalancerNatPool_basic(t *testing.T) { }) } +func TestAccAzureRMLoadBalancerNatPool_requiresImport(t *testing.T) { + var lb network.LoadBalancer + ri := acctest.RandInt() + natPoolName := fmt.Sprintf("NatPool-%d", ri) + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMLoadBalancerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMLoadBalancerNatPool_basic(ri, natPoolName, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMLoadBalancerExists("azurerm_lb.test", &lb), + ), + }, + { + Config: testAccAzureRMLoadBalancerNatPool_requiresImport(ri, natPoolName, location), + ExpectError: testRequiresImportError("azurerm_lb_nat_pool"), + }, + }, + }) +} + func TestAccAzureRMLoadBalancerNatPool_removal(t *testing.T) { var lb network.LoadBalancer ri := acctest.RandInt() @@ -166,7 +191,7 @@ func TestAccAzureRMLoadBalancerNatPool_disappears(t *testing.T) { func testCheckAzureRMLoadBalancerNatPoolExists(natPoolName string, lb *network.LoadBalancer) resource.TestCheckFunc { return func(s *terraform.State) error { - _, _, exists := findLoadBalancerNatPoolByName(lb, natPoolName) + _, exists := findAzureRMLoadBalancerNatPoolByName(lb, natPoolName) if !exists { return fmt.Errorf("A NAT Pool with name %q cannot be found.", natPoolName) } @@ -177,7 +202,7 @@ func testCheckAzureRMLoadBalancerNatPoolExists(natPoolName string, lb *network.L func testCheckAzureRMLoadBalancerNatPoolNotExists(natPoolName string, lb *network.LoadBalancer) resource.TestCheckFunc { return func(s *terraform.State) error { - _, _, exists := findLoadBalancerNatPoolByName(lb, natPoolName) + _, exists := findAzureRMLoadBalancerNatPoolByName(lb, natPoolName) if exists { return fmt.Errorf("A NAT Pool with name %q has been found.", natPoolName) } @@ -191,7 +216,7 @@ func testCheckAzureRMLoadBalancerNatPoolDisappears(natPoolName string, lb *netwo client := testAccProvider.Meta().(*ArmClient).loadBalancerClient ctx := testAccProvider.Meta().(*ArmClient).StopContext - _, i, exists := findLoadBalancerNatPoolByName(lb, natPoolName) + i, exists := findAzureRMLoadBalancerNatPoolByName(lb, natPoolName) if !exists { return fmt.Errorf("A Nat Pool with name %q cannot be found.", natPoolName) } @@ -220,6 +245,20 @@ func testCheckAzureRMLoadBalancerNatPoolDisappears(natPoolName string, lb *netwo } } +func findAzureRMLoadBalancerNatPoolByName(lb *network.LoadBalancer, name string) (int, bool) { + if lb == nil || lb.LoadBalancerPropertiesFormat == nil || lb.LoadBalancerPropertiesFormat.InboundNatPools == nil { + return -1, false + } + + for i, np := range *lb.LoadBalancerPropertiesFormat.InboundNatPools { + if np.Name != nil && *np.Name == name { + return i, true + } + } + + return -1, false +} + func testAccAzureRMLoadBalancerNatPool_basic(rInt int, natPoolName string, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { @@ -259,6 +298,25 @@ resource "azurerm_lb_nat_pool" "test" { `, rInt, location, rInt, rInt, rInt, natPoolName, rInt) } +func testAccAzureRMLoadBalancerNatPool_requiresImport(rInt int, natPoolName string, location string) string { + template := testAccAzureRMLoadBalancerNatPool_basic(rInt, natPoolName, location) + return fmt.Sprintf(` +%s + +resource "azurerm_lb_nat_pool" "import" { + name = "${azurerm_lb_nat_pool.test.name}" + location = "${azurerm_lb_nat_pool.test.location}" + resource_group_name = "${azurerm_lb_nat_pool.test.resource_group_name}" + loadbalancer_id = "${azurerm_lb_nat_pool.test.loadbalancer_id}" + protocol = "${azurerm_lb_nat_pool.test.protocol}" + frontend_port_start = "${azurerm_lb_nat_pool.test.frontend_port_start}" + frontend_port_end = "${azurerm_lb_nat_pool.test.frontend_port_end}" + backend_port = "${azurerm_lb_nat_pool.test.backend_port}" + frontend_ip_configuration_name = "${azurerm_lb_nat_pool.test.frontend_ip_configuration_name}" +} +`, template) +} + func testAccAzureRMLoadBalancerNatPool_removal(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_loadbalancer_nat_rule.go b/azurerm/resource_arm_loadbalancer_nat_rule.go index 01570e0c8fb2..9ce18c2363f7 100644 --- a/azurerm/resource_arm_loadbalancer_nat_rule.go +++ b/azurerm/resource_arm_loadbalancer_nat_rule.go @@ -1,16 +1,17 @@ package azurerm import ( + "context" "fmt" "log" "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" - "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/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/utils" ) @@ -21,10 +22,14 @@ func resourceArmLoadBalancerNatRule() *schema.Resource { Read: resourceArmLoadBalancerNatRuleRead, Update: resourceArmLoadBalancerNatRuleCreateUpdate, Delete: resourceArmLoadBalancerNatRuleDelete, - Importer: &schema.ResourceImporter{ State: loadBalancerSubResourceStateImporter, }, + 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": { @@ -98,7 +103,9 @@ func resourceArmLoadBalancerNatRuleCreateUpdate(d *schema.ResourceData, meta int client := meta.(*ArmClient).loadBalancerClient ctx := meta.(*ArmClient).StopContext + name := d.Get("name").(string) loadBalancerID := d.Get("loadbalancer_id").(string) + armMutexKV.Lock(loadBalancerID) defer armMutexKV.Unlock(loadBalancerID) @@ -108,27 +115,54 @@ func resourceArmLoadBalancerNatRuleCreateUpdate(d *schema.ResourceData, meta int } if !exists { d.SetId("") - log.Printf("[INFO] Load Balancer %q not found. Removing from state", d.Get("name").(string)) + log.Printf("[INFO] Load Balancer %q not found. Removing from state", name) return nil } + props := loadBalancer.LoadBalancerPropertiesFormat + if props == nil { + return fmt.Errorf("Error updating Load Inbound NAT Rule: props was nil") + } + + rules := props.InboundNatRules + if rules == nil { + return fmt.Errorf("Error updating Load Balancer Inbound NAT Rule: props.InboundNatRules was nil") + } + newNatRule, err := expandAzureRmLoadBalancerNatRule(d, loadBalancer) if err != nil { return fmt.Errorf("Error Expanding NAT Rule: %+v", err) } - natRules := append(*loadBalancer.LoadBalancerPropertiesFormat.InboundNatRules, *newNatRule) + natRules := *rules + if d.IsNewResource() { + for _, rule := range natRules { + if rule.Name != nil && *rule.Name == name { + return tf.ImportAsExistsError("azurerm_lb_nat_rule", *rule.ID) + } + } + + natRules = append(natRules, *newNatRule) + } else { + // find and replace + index := -1 + for i, v := range natRules { + if v.Name != nil && *v.Name == name { + index = i + break + } + } - existingNatRule, existingNatRuleIndex, exists := findLoadBalancerNatRuleByName(loadBalancer, d.Get("name").(string)) - if exists { - if d.Get("name").(string) == *existingNatRule.Name { - // this nat rule is being updated/reapplied remove old copy from the slice - natRules = append(natRules[:existingNatRuleIndex], natRules[existingNatRuleIndex+1:]...) + if index == -1 { + // note: this should be caught by the read + return fmt.Errorf("Error: Inbound Nat Rule %q was not found on Load Balancer %q", name, loadBalancerID) } + + natRules[index] = *newNatRule } loadBalancer.LoadBalancerPropertiesFormat.InboundNatRules = &natRules - resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(d.Get("loadbalancer_id").(string)) + resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(loadBalancerID) if err != nil { return fmt.Errorf("Error Getting Load Balancer Name and Group: %+v", err) } @@ -138,7 +172,9 @@ func resourceArmLoadBalancerNatRuleCreateUpdate(d *schema.ResourceData, meta int return fmt.Errorf("Error Creating / Updating Load Balancer %q (Resource Group %q): %+v", loadBalancerName, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for completion of Load Balancer %q (Resource Group %q): %+v", loadBalancerName, resGroup, err) } @@ -153,29 +189,21 @@ func resourceArmLoadBalancerNatRuleCreateUpdate(d *schema.ResourceData, meta int } var natRuleId string - for _, InboundNatRule := range *(*read.LoadBalancerPropertiesFormat).InboundNatRules { - if *InboundNatRule.Name == d.Get("name").(string) { - natRuleId = *InboundNatRule.ID + if props := read.LoadBalancerPropertiesFormat; props != nil { + if rules := props.InboundNatRules; rules != nil { + for _, rule := range *rules { + if rule.Name != nil && *rule.Name == name { + natRuleId = *rule.ID + } + } } } - if natRuleId != "" { - d.SetId(natRuleId) - } else { + if natRuleId == "" { return fmt.Errorf("Cannot find created Load Balancer NAT Rule ID %q", natRuleId) } - // TODO: is this still needed? - log.Printf("[DEBUG] Waiting for Load Balancer (%s) to become available", loadBalancerName) - stateConf := &resource.StateChangeConf{ - Pending: []string{"Accepted", "Updating"}, - Target: []string{"Succeeded"}, - Refresh: loadbalancerStateRefreshFunc(ctx, client, resGroup, loadBalancerName), - Timeout: 10 * time.Minute, - } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf("Error waiting for Load Balancer (%s) to become available: %s", loadBalancerName, err) - } + d.SetId(natRuleId) return resourceArmLoadBalancerNatRuleRead(d, meta) } @@ -186,8 +214,8 @@ func resourceArmLoadBalancerNatRuleRead(d *schema.ResourceData, meta interface{} return err } name := id.Path["inboundNatRules"] - - loadBalancer, exists, err := retrieveLoadBalancerById(d.Get("loadbalancer_id").(string), meta) + loadBalancerId := d.Get("loadbalancer_id").(string) + loadBalancer, exists, err := retrieveLoadBalancerById(loadBalancerId, meta) if err != nil { return fmt.Errorf("Error Getting Load Balancer By ID: %+v", err) } @@ -197,17 +225,28 @@ func resourceArmLoadBalancerNatRuleRead(d *schema.ResourceData, meta interface{} return nil } - config, _, exists := findLoadBalancerNatRuleByName(loadBalancer, name) - if !exists { + var rule *network.InboundNatRule + if props := loadBalancer.LoadBalancerPropertiesFormat; props != nil { + if rules := props.InboundNatRules; rules != nil { + for _, r := range *rules { + if r.Name != nil && *r.Name == name { + rule = &r + break + } + } + } + } + + if rule == nil { d.SetId("") log.Printf("[INFO] Load Balancer Nat Rule %q not found. Removing from state", name) return nil } - d.Set("name", config.Name) + d.Set("name", name) d.Set("resource_group_name", id.ResourceGroup) - if props := config.InboundNatRulePropertiesFormat; props != nil { + if props := rule.InboundNatRulePropertiesFormat; props != nil { d.Set("protocol", props.Protocol) d.Set("frontend_port", props.FrontendPort) d.Set("backend_port", props.BackendPort) @@ -235,7 +274,9 @@ func resourceArmLoadBalancerNatRuleDelete(d *schema.ResourceData, meta interface client := meta.(*ArmClient).loadBalancerClient ctx := meta.(*ArmClient).StopContext + name := d.Get("name").(string) loadBalancerID := d.Get("loadbalancer_id").(string) + armMutexKV.Lock(loadBalancerID) defer armMutexKV.Unlock(loadBalancerID) @@ -248,26 +289,38 @@ func resourceArmLoadBalancerNatRuleDelete(d *schema.ResourceData, meta interface return nil } - _, index, exists := findLoadBalancerNatRuleByName(loadBalancer, d.Get("name").(string)) - if !exists { + props := loadBalancer.LoadBalancerPropertiesFormat + if props == nil { return nil } - oldNatRules := *loadBalancer.LoadBalancerPropertiesFormat.InboundNatRules - newNatRules := append(oldNatRules[:index], oldNatRules[index+1:]...) - loadBalancer.LoadBalancerPropertiesFormat.InboundNatRules = &newNatRules + rules := props.InboundNatRules + if rules == nil { + return nil + } + + natRules := make([]network.InboundNatRule, 0) + for _, rule := range *rules { + if rule.Name != nil && *rule.Name != name { + natRules = append(natRules, rule) + } + } + + loadBalancer.LoadBalancerPropertiesFormat.InboundNatRules = &natRules - resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(d.Get("loadbalancer_id").(string)) + resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(loadBalancerID) if err != nil { return fmt.Errorf("Error Getting Load Balancer Name and Group: %+v", err) } future, err := client.CreateOrUpdate(ctx, resGroup, loadBalancerName, *loadBalancer) if err != nil { - return fmt.Errorf("Error Creating/Updating Load Balancer %q (Resource Group %q) %+v", loadBalancerName, resGroup, err) + return fmt.Errorf("Error Updating Load Balancer %q (Resource Group %q) %+v", loadBalancerName, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for the completion of Load Balancer updates for %q (Resource Group %q) %+v", loadBalancerName, resGroup, err) } @@ -284,7 +337,6 @@ func resourceArmLoadBalancerNatRuleDelete(d *schema.ResourceData, meta interface } func expandAzureRmLoadBalancerNatRule(d *schema.ResourceData, lb *network.LoadBalancer) (*network.InboundNatRule, error) { - properties := network.InboundNatRulePropertiesFormat{ Protocol: network.TransportProtocol(d.Get("protocol").(string)), FrontendPort: utils.Int32(int32(d.Get("frontend_port").(int))), diff --git a/azurerm/resource_arm_loadbalancer_nat_rule_test.go b/azurerm/resource_arm_loadbalancer_nat_rule_test.go index 3b8572dc8527..5286553388a5 100644 --- a/azurerm/resource_arm_loadbalancer_nat_rule_test.go +++ b/azurerm/resource_arm_loadbalancer_nat_rule_test.go @@ -46,6 +46,31 @@ func TestAccAzureRMLoadBalancerNatRule_basic(t *testing.T) { }) } +func TestAccAzureRMLoadBalancerNatRule_requiresImport(t *testing.T) { + var lb network.LoadBalancer + ri := acctest.RandInt() + location := testLocation() + natRuleName := fmt.Sprintf("NatRule-%d", ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMLoadBalancerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMLoadBalancerNatRule_basic(ri, natRuleName, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMLoadBalancerExists("azurerm_lb.test", &lb), + ), + }, + { + Config: testAccAzureRMLoadBalancerNatRule_requiresImport(ri, natRuleName, location), + ExpectError: testRequiresImportError("azurerm_lb_nat_rule"), + }, + }, + }) +} + func TestAccAzureRMLoadBalancerNatRule_removal(t *testing.T) { var lb network.LoadBalancer ri := acctest.RandInt() @@ -226,7 +251,7 @@ func TestAccAzureRMLoadBalancerNatRule_disableFloatingIP(t *testing.T) { func testCheckAzureRMLoadBalancerNatRuleExists(natRuleName string, lb *network.LoadBalancer) resource.TestCheckFunc { return func(s *terraform.State) error { - _, _, exists := findLoadBalancerNatRuleByName(lb, natRuleName) + _, exists := findAzureRMLoadBalancerNatRuleByName(lb, natRuleName) if !exists { return fmt.Errorf("A NAT Rule with name %q cannot be found.", natRuleName) } @@ -237,7 +262,7 @@ func testCheckAzureRMLoadBalancerNatRuleExists(natRuleName string, lb *network.L func testCheckAzureRMLoadBalancerNatRuleNotExists(natRuleName string, lb *network.LoadBalancer) resource.TestCheckFunc { return func(s *terraform.State) error { - _, _, exists := findLoadBalancerNatRuleByName(lb, natRuleName) + _, exists := findAzureRMLoadBalancerNatRuleByName(lb, natRuleName) if exists { return fmt.Errorf("A NAT Rule with name %q has been found.", natRuleName) } @@ -251,7 +276,7 @@ func testCheckAzureRMLoadBalancerNatRuleDisappears(natRuleName string, lb *netwo client := testAccProvider.Meta().(*ArmClient).loadBalancerClient ctx := testAccProvider.Meta().(*ArmClient).StopContext - _, i, exists := findLoadBalancerNatRuleByName(lb, natRuleName) + i, exists := findAzureRMLoadBalancerNatRuleByName(lb, natRuleName) if !exists { return fmt.Errorf("A Nat Rule with name %q cannot be found.", natRuleName) } @@ -280,6 +305,20 @@ func testCheckAzureRMLoadBalancerNatRuleDisappears(natRuleName string, lb *netwo } } +func findAzureRMLoadBalancerNatRuleByName(lb *network.LoadBalancer, name string) (int, bool) { + if lb == nil || lb.LoadBalancerPropertiesFormat == nil || lb.LoadBalancerPropertiesFormat.InboundNatRules == nil { + return -1, false + } + + for i, nr := range *lb.LoadBalancerPropertiesFormat.InboundNatRules { + if nr.Name != nil && *nr.Name == name { + return i, true + } + } + + return -1, false +} + func testAccAzureRMLoadBalancerNatRule_basic(rInt int, natRuleName string, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { @@ -318,6 +357,24 @@ resource "azurerm_lb_nat_rule" "test" { `, rInt, location, rInt, rInt, rInt, natRuleName, rInt) } +func testAccAzureRMLoadBalancerNatRule_requiresImport(rInt int, natRuleName string, location string) string { + template := testAccAzureRMLoadBalancerNatRule_basic(rInt, natRuleName, location) + return fmt.Sprintf(` +%s + +resource "azurerm_lb_nat_rule" "import" { + name = "${azurerm_lb_nat_rule.test.name}" + location = "${azurerm_lb_nat_rule.test.location}" + resource_group_name = "${azurerm_lb_nat_rule.test.resource_group_name}" + loadbalancer_id = "${azurerm_lb_nat_rule.test.loadbalancer_id}" + protocol = "${azurerm_lb_nat_rule.test.protocol}" + frontend_port = "${azurerm_lb_nat_rule.test.frontend_port}" + backend_port = "${azurerm_lb_nat_rule.test.backend_port}" + frontend_ip_configuration_name = "${azurerm_lb_nat_rule.test.frontend_ip_configuration_name}" +} +`, template) +} + func testAccAzureRMLoadBalancerNatRule_removal(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_loadbalancer_probe.go b/azurerm/resource_arm_loadbalancer_probe.go index 64be089c446e..8a3161f5a99b 100644 --- a/azurerm/resource_arm_loadbalancer_probe.go +++ b/azurerm/resource_arm_loadbalancer_probe.go @@ -1,16 +1,17 @@ package azurerm import ( + "context" "fmt" "log" "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" - "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/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/utils" ) @@ -24,6 +25,11 @@ func resourceArmLoadBalancerProbe() *schema.Resource { Importer: &schema.ResourceImporter{ State: loadBalancerSubResourceStateImporter, }, + 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": { @@ -98,7 +104,9 @@ func resourceArmLoadBalancerProbeCreateUpdate(d *schema.ResourceData, meta inter client := meta.(*ArmClient).loadBalancerClient ctx := meta.(*ArmClient).StopContext + name := d.Get("name").(string) loadBalancerID := d.Get("loadbalancer_id").(string) + armMutexKV.Lock(loadBalancerID) defer armMutexKV.Unlock(loadBalancerID) @@ -108,23 +116,52 @@ func resourceArmLoadBalancerProbeCreateUpdate(d *schema.ResourceData, meta inter } if !exists { d.SetId("") - log.Printf("[INFO] Load Balancer %q not found. Removing from state", d.Get("name").(string)) + log.Printf("[INFO] Load Balancer %q not found. Removing from state", name) return nil } + props := loadBalancer.LoadBalancerPropertiesFormat + if props == nil { + return fmt.Errorf("Error updating Load Balancer Probes: props was nil") + } + + probes := props.Probes + if probes == nil { + return fmt.Errorf("Error updating Load Balancer Probes: props.Probes was nil") + } + existingProbes := *probes + newProbe := expandAzureRmLoadBalancerProbe(d) - probes := append(*loadBalancer.LoadBalancerPropertiesFormat.Probes, *newProbe) + if d.IsNewResource() { + // firstly check if it exists + for _, probe := range existingProbes { + if probe.Name != nil && *probe.Name == name { + return tf.ImportAsExistsError("azurerm_lb_probe", *probe.ID) + } + } - existingProbe, existingProbeIndex, exists := findLoadBalancerProbeByName(loadBalancer, d.Get("name").(string)) - if exists { - if d.Get("name").(string) == *existingProbe.Name { - // this probe is being updated/reapplied remove old copy from the slice - probes = append(probes[:existingProbeIndex], probes[existingProbeIndex+1:]...) + // then append it if not + existingProbes = append(existingProbes, *newProbe) + } else { + // swap it in-place + index := -1 + for i, probe := range existingProbes { + if probe.Name != nil && *probe.Name == name { + index = i + break + } } + + if index == -1 { + // should have been caught by the Read + return fmt.Errorf("Error: Probe %q was not found on Load Balancer %q", name, loadBalancerID) + } + + existingProbes[index] = *newProbe } - loadBalancer.LoadBalancerPropertiesFormat.Probes = &probes - resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(d.Get("loadbalancer_id").(string)) + loadBalancer.LoadBalancerPropertiesFormat.Probes = &existingProbes + resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(loadBalancerID) if err != nil { return fmt.Errorf("Error Getting Load Balancer Name and Group: %+v", err) } @@ -134,7 +171,9 @@ func resourceArmLoadBalancerProbeCreateUpdate(d *schema.ResourceData, meta inter return fmt.Errorf("Error Creating/Updating Load Balancer %q (Resource Group %q): %+v", loadBalancerName, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for completion of Load Balancer %q (Resource Group %q): %+v", loadBalancerName, resGroup, err) } @@ -147,29 +186,22 @@ func resourceArmLoadBalancerProbeCreateUpdate(d *schema.ResourceData, meta inter return fmt.Errorf("Cannot read Load Balancer %q (resource group %q) ID", loadBalancerName, resGroup) } - var createdProbeId string - for _, Probe := range *(*read.LoadBalancerPropertiesFormat).Probes { - if *Probe.Name == d.Get("name").(string) { - createdProbeId = *Probe.ID + var probeId string + if props := read.LoadBalancerPropertiesFormat; props != nil { + if probes := props.Probes; probes != nil { + for _, probe := range *probes { + if *probe.Name == name { + probeId = *probe.ID + } + } } } - if createdProbeId == "" { - return fmt.Errorf("Cannot find created Load Balancer Probe ID %q", createdProbeId) + if probeId == "" { + return fmt.Errorf("Cannot find created Load Balancer Probe ID %q", probeId) } - d.SetId(createdProbeId) - - log.Printf("[DEBUG] Waiting for Load Balancer (%s) to become available", loadBalancerName) - stateConf := &resource.StateChangeConf{ - Pending: []string{"Accepted", "Updating"}, - Target: []string{"Succeeded"}, - Refresh: loadbalancerStateRefreshFunc(ctx, client, resGroup, loadBalancerName), - Timeout: 10 * time.Minute, - } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf("Error waiting for Load Balancer (%q - Resource Group %q) to become available: %+v", loadBalancerName, resGroup, err) - } + d.SetId(probeId) return resourceArmLoadBalancerProbeRead(d, meta) } @@ -180,8 +212,9 @@ func resourceArmLoadBalancerProbeRead(d *schema.ResourceData, meta interface{}) return err } name := id.Path["probes"] + loadBalancerId := d.Get("loadbalancer_id").(string) - loadBalancer, exists, err := retrieveLoadBalancerById(d.Get("loadbalancer_id").(string), meta) + loadBalancer, exists, err := retrieveLoadBalancerById(loadBalancerId, meta) if err != nil { return fmt.Errorf("Error Getting Load Balancer By ID: %+v", err) } @@ -191,33 +224,44 @@ func resourceArmLoadBalancerProbeRead(d *schema.ResourceData, meta interface{}) return nil } - config, _, exists := findLoadBalancerProbeByName(loadBalancer, name) - if !exists { - d.SetId("") + var probe *network.Probe + if props := loadBalancer.LoadBalancerPropertiesFormat; props != nil { + if probes := props.Probes; probes != nil { + for _, p := range *probes { + if p.Name != nil && *p.Name == name { + probe = &p + break + } + } + } + } + + if probe == nil { log.Printf("[INFO] Load Balancer Probe %q not found. Removing from state", name) + d.SetId("") return nil } - d.Set("name", config.Name) + d.Set("name", name) d.Set("resource_group_name", id.ResourceGroup) - if properties := config.ProbePropertiesFormat; properties != nil { + if properties := probe.ProbePropertiesFormat; properties != nil { d.Set("protocol", properties.Protocol) d.Set("interval_in_seconds", properties.IntervalInSeconds) d.Set("number_of_probes", properties.NumberOfProbes) d.Set("port", properties.Port) d.Set("request_path", properties.RequestPath) - var load_balancer_rules []string + loadBalancerRules := make([]string, 0) if rules := properties.LoadBalancingRules; rules != nil { for _, ruleConfig := range *rules { if id := ruleConfig.ID; id != nil { - load_balancer_rules = append(load_balancer_rules, *id) + loadBalancerRules = append(loadBalancerRules, *id) } } } - if err := d.Set("load_balancer_rules", load_balancer_rules); err != nil { - return fmt.Errorf("Error setting `load_balancer_rules` (Load Balancer Probe %q): %+v", name, err) + if err := d.Set("load_balancer_rules", loadBalancerRules); err != nil { + return fmt.Errorf("Error setting `load_balancer_rules`: %+v", err) } } @@ -228,7 +272,9 @@ func resourceArmLoadBalancerProbeDelete(d *schema.ResourceData, meta interface{} client := meta.(*ArmClient).loadBalancerClient ctx := meta.(*ArmClient).StopContext + name := d.Get("name").(string) loadBalancerID := d.Get("loadbalancer_id").(string) + armMutexKV.Lock(loadBalancerID) defer armMutexKV.Unlock(loadBalancerID) @@ -241,16 +287,30 @@ func resourceArmLoadBalancerProbeDelete(d *schema.ResourceData, meta interface{} return nil } - _, index, exists := findLoadBalancerProbeByName(loadBalancer, d.Get("name").(string)) - if !exists { + props := loadBalancer.LoadBalancerPropertiesFormat + if props == nil { + log.Printf("[INFO] props was nil for Load Balancer %q - removing from state", loadBalancerID) + d.SetId("") + return nil + } + + probes := props.Probes + if probes == nil { + log.Printf("[INFO] props.Probes was nil for Load Balancer %q - removing from state", loadBalancerID) + d.SetId("") return nil } - oldProbes := *loadBalancer.LoadBalancerPropertiesFormat.Probes - newProbes := append(oldProbes[:index], oldProbes[index+1:]...) + newProbes := make([]network.Probe, 0) + for _, probe := range *probes { + if probe.Name != nil && *probe.Name != name { + newProbes = append(newProbes, probe) + } + } + loadBalancer.LoadBalancerPropertiesFormat.Probes = &newProbes - resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(d.Get("loadbalancer_id").(string)) + resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(loadBalancerID) if err != nil { return fmt.Errorf("Error Getting Load Balancer Name and Group:: %+v", err) } @@ -260,7 +320,9 @@ func resourceArmLoadBalancerProbeDelete(d *schema.ResourceData, meta interface{} return fmt.Errorf("Error Creating/Updating Load Balancer %q (Resource Group %q): %+v", loadBalancerName, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for completion of Load Balancer %q (Resource Group %q): %+v", loadBalancerName, resGroup, err) } @@ -277,7 +339,6 @@ func resourceArmLoadBalancerProbeDelete(d *schema.ResourceData, meta interface{} } func expandAzureRmLoadBalancerProbe(d *schema.ResourceData) *network.Probe { - properties := network.ProbePropertiesFormat{ NumberOfProbes: utils.Int32(int32(d.Get("number_of_probes").(int))), IntervalInSeconds: utils.Int32(int32(d.Get("interval_in_seconds").(int))), diff --git a/azurerm/resource_arm_loadbalancer_probe_test.go b/azurerm/resource_arm_loadbalancer_probe_test.go index 7795616ff070..058c0d898a78 100644 --- a/azurerm/resource_arm_loadbalancer_probe_test.go +++ b/azurerm/resource_arm_loadbalancer_probe_test.go @@ -46,6 +46,31 @@ func TestAccAzureRMLoadBalancerProbe_basic(t *testing.T) { }) } +func TestAccAzureRMLoadBalancerProbe_requiresImport(t *testing.T) { + var lb network.LoadBalancer + ri := acctest.RandInt() + location := testLocation() + probeName := fmt.Sprintf("probe-%d", ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMLoadBalancerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMLoadBalancerProbe_basic(ri, probeName, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMLoadBalancerExists("azurerm_lb.test", &lb), + ), + }, + { + Config: testAccAzureRMLoadBalancerProbe_requiresImport(ri, probeName, location), + ExpectError: testRequiresImportError("azurerm_lb_probe"), + }, + }, + }) +} + func TestAccAzureRMLoadBalancerProbe_removal(t *testing.T) { var lb network.LoadBalancer ri := acctest.RandInt() @@ -197,7 +222,7 @@ func TestAccAzureRMLoadBalancerProbe_disappears(t *testing.T) { func testCheckAzureRMLoadBalancerProbeExists(natRuleName string, lb *network.LoadBalancer) resource.TestCheckFunc { return func(s *terraform.State) error { - _, _, exists := findLoadBalancerProbeByName(lb, natRuleName) + _, exists := findAzureRMLoadBalancerProbeByName(lb, natRuleName) if !exists { return fmt.Errorf("A Probe with name %q cannot be found.", natRuleName) } @@ -208,7 +233,7 @@ func testCheckAzureRMLoadBalancerProbeExists(natRuleName string, lb *network.Loa func testCheckAzureRMLoadBalancerProbeNotExists(natRuleName string, lb *network.LoadBalancer) resource.TestCheckFunc { return func(s *terraform.State) error { - _, _, exists := findLoadBalancerProbeByName(lb, natRuleName) + _, exists := findAzureRMLoadBalancerProbeByName(lb, natRuleName) if exists { return fmt.Errorf("A Probe with name %q has been found.", natRuleName) } @@ -222,7 +247,7 @@ func testCheckAzureRMLoadBalancerProbeDisappears(addressPoolName string, lb *net client := testAccProvider.Meta().(*ArmClient).loadBalancerClient ctx := testAccProvider.Meta().(*ArmClient).StopContext - _, i, exists := findLoadBalancerProbeByName(lb, addressPoolName) + i, exists := findAzureRMLoadBalancerProbeByName(lb, addressPoolName) if !exists { return fmt.Errorf("A Probe with name %q cannot be found.", addressPoolName) } @@ -251,6 +276,20 @@ func testCheckAzureRMLoadBalancerProbeDisappears(addressPoolName string, lb *net } } +func findAzureRMLoadBalancerProbeByName(lb *network.LoadBalancer, name string) (int, bool) { + if lb == nil || lb.LoadBalancerPropertiesFormat == nil || lb.LoadBalancerPropertiesFormat.Probes == nil { + return -1, false + } + + for i, p := range *lb.LoadBalancerPropertiesFormat.Probes { + if p.Name != nil && *p.Name == name { + return i, true + } + } + + return -1, false +} + func testAccAzureRMLoadBalancerProbe_basic(rInt int, probeName string, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { @@ -286,6 +325,22 @@ resource "azurerm_lb_probe" "test" { `, rInt, location, rInt, rInt, rInt, probeName) } +func testAccAzureRMLoadBalancerProbe_requiresImport(rInt int, probeName string, location string) string { + template := testAccAzureRMLoadBalancerProbe_basic(rInt, probeName, location) + return fmt.Sprintf( + ` +%s + +resource "azurerm_lb_probe" "import" { + name = "${azurerm_lb_probe.test.name}" + location = "${azurerm_lb_probe.test.location}" + resource_group_name = "${azurerm_lb_probe.test.resource_group_name}" + loadbalancer_id = "${azurerm_lb_probe.test.loadbalancer_id}" + port = "${azurerm_lb_probe.test.port}" +} +`, template) +} + func testAccAzureRMLoadBalancerProbe_removal(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_loadbalancer_rule.go b/azurerm/resource_arm_loadbalancer_rule.go index 44ca3c0b3322..206ef7dd41e0 100644 --- a/azurerm/resource_arm_loadbalancer_rule.go +++ b/azurerm/resource_arm_loadbalancer_rule.go @@ -1,31 +1,36 @@ package azurerm import ( + "context" "fmt" "log" "regexp" "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" - "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/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/utils" ) func resourceArmLoadBalancerRule() *schema.Resource { return &schema.Resource{ - Create: resourceArmLoadBalancerRuleCreate, + Create: resourceArmLoadBalancerRuleCreateUpdate, Read: resourceArmLoadBalancerRuleRead, - Update: resourceArmLoadBalancerRuleCreate, + Update: resourceArmLoadBalancerRuleCreateUpdate, Delete: resourceArmLoadBalancerRuleDelete, - Importer: &schema.ResourceImporter{ State: loadBalancerSubResourceStateImporter, }, + 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": { @@ -115,10 +120,11 @@ func resourceArmLoadBalancerRule() *schema.Resource { } } -func resourceArmLoadBalancerRuleCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmLoadBalancerRuleCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).loadBalancerClient ctx := meta.(*ArmClient).StopContext + name := d.Get("name").(string) loadBalancerID := d.Get("loadbalancer_id").(string) armMutexKV.Lock(loadBalancerID) defer armMutexKV.Unlock(loadBalancerID) @@ -129,27 +135,54 @@ func resourceArmLoadBalancerRuleCreate(d *schema.ResourceData, meta interface{}) } if !exists { d.SetId("") - log.Printf("[INFO] Load Balancer %q not found. Removing from state", d.Get("name").(string)) + log.Printf("[INFO] Load Balancer %q not found. Removing from state", name) return nil } + props := loadBalancer.LoadBalancerPropertiesFormat + if props == nil { + return fmt.Errorf("Error updating Load Balancer Rules: props was nil") + } + + rules := props.LoadBalancingRules + if rules == nil { + return fmt.Errorf("Error updating Load Balancer Rules: props.LoadBalancingRules was nil") + } + lbRules := *rules + newLbRule, err := expandAzureRmLoadBalancerRule(d, loadBalancer) if err != nil { return fmt.Errorf("Error Exanding Load Balancer Rule: %+v", err) } - lbRules := append(*loadBalancer.LoadBalancerPropertiesFormat.LoadBalancingRules, *newLbRule) + if d.IsNewResource() { + // check if it requires import + for _, rule := range lbRules { + if rule.Name != nil && *rule.Name == name { + return tf.ImportAsExistsError("azurerm_lb_rule", *rule.ID) + } + } + + // otherwise just append it + lbRules = append(lbRules, *newLbRule) + } else { + index := -1 + for i, v := range lbRules { + if v.Name != nil && *v.Name == name { + index = i + break + } + } - existingRule, existingRuleIndex, exists := findLoadBalancerRuleByName(loadBalancer, d.Get("name").(string)) - if exists { - if d.Get("name").(string) == *existingRule.Name { - // this rule is being updated/reapplied remove old copy from the slice - lbRules = append(lbRules[:existingRuleIndex], lbRules[existingRuleIndex+1:]...) + if index == -1 { + // should be caught by the Read } + + lbRules[index] = *newLbRule } loadBalancer.LoadBalancerPropertiesFormat.LoadBalancingRules = &lbRules - resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(d.Get("loadbalancer_id").(string)) + resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(loadBalancerID) if err != nil { return fmt.Errorf("Error Getting Load Balancer Name and Group:: %+v", err) } @@ -159,7 +192,9 @@ func resourceArmLoadBalancerRuleCreate(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error Creating/Updating LoadBalancer: %+v", err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for completion for Load Balancer updates: %+v", err) } @@ -173,9 +208,13 @@ func resourceArmLoadBalancerRuleCreate(d *schema.ResourceData, meta interface{}) } var ruleId string - for _, LoadBalancingRule := range *(*read.LoadBalancerPropertiesFormat).LoadBalancingRules { - if *LoadBalancingRule.Name == d.Get("name").(string) { - ruleId = *LoadBalancingRule.ID + if props := read.LoadBalancerPropertiesFormat; props != nil { + if rules := props.LoadBalancingRules; rules != nil { + for _, rule := range *rules { + if rule.Name != nil && *rule.Name == name { + ruleId = *rule.ID + } + } } } @@ -185,17 +224,6 @@ func resourceArmLoadBalancerRuleCreate(d *schema.ResourceData, meta interface{}) d.SetId(ruleId) - log.Printf("[DEBUG] Waiting for Load Balancer (%s) to become available", loadBalancerName) - stateConf := &resource.StateChangeConf{ - Pending: []string{"Accepted", "Updating"}, - Target: []string{"Succeeded"}, - Refresh: loadbalancerStateRefreshFunc(ctx, client, resGroup, loadBalancerName), - Timeout: 10 * time.Minute, - } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf("Error waiting for Load Balancer (%s) to become available: %s", loadBalancerName, err) - } - return resourceArmLoadBalancerRuleRead(d, meta) } @@ -205,8 +233,9 @@ func resourceArmLoadBalancerRuleRead(d *schema.ResourceData, meta interface{}) e return err } name := id.Path["loadBalancingRules"] + loadBalancerId := d.Get("loadbalancer_id").(string) - loadBalancer, exists, err := retrieveLoadBalancerById(d.Get("loadbalancer_id").(string), meta) + loadBalancer, exists, err := retrieveLoadBalancerById(loadBalancerId, meta) if err != nil { return fmt.Errorf("Error Getting Load Balancer By ID: %+v", err) } @@ -216,17 +245,28 @@ func resourceArmLoadBalancerRuleRead(d *schema.ResourceData, meta interface{}) e return nil } - config, _, exists := findLoadBalancerRuleByName(loadBalancer, name) - if !exists { + var rule *network.LoadBalancingRule + if props := loadBalancer.LoadBalancerPropertiesFormat; props != nil { + if rules := props.LoadBalancingRules; rules != nil { + for _, r := range *rules { + if r.Name != nil && *r.Name == name { + rule = &r + break + } + } + } + } + + if rule == nil { d.SetId("") log.Printf("[INFO] Load Balancer Rule %q not found. Removing from state", name) return nil } - d.Set("name", config.Name) + d.Set("name", name) d.Set("resource_group_name", id.ResourceGroup) - if properties := config.LoadBalancingRulePropertiesFormat; properties != nil { + if properties := rule.LoadBalancingRulePropertiesFormat; properties != nil { d.Set("protocol", properties.Protocol) d.Set("frontend_port", properties.FrontendPort) d.Set("backend_port", properties.BackendPort) @@ -269,7 +309,9 @@ func resourceArmLoadBalancerRuleDelete(d *schema.ResourceData, meta interface{}) client := meta.(*ArmClient).loadBalancerClient ctx := meta.(*ArmClient).StopContext + name := d.Get("name").(string) loadBalancerID := d.Get("loadbalancer_id").(string) + armMutexKV.Lock(loadBalancerID) defer armMutexKV.Unlock(loadBalancerID) @@ -282,26 +324,38 @@ func resourceArmLoadBalancerRuleDelete(d *schema.ResourceData, meta interface{}) return nil } - _, index, exists := findLoadBalancerRuleByName(loadBalancer, d.Get("name").(string)) - if !exists { - return nil + props := loadBalancer.LoadBalancerPropertiesFormat + if props == nil { + return fmt.Errorf("Error updating Load Balancer Rules: props was nil") } - oldLbRules := *loadBalancer.LoadBalancerPropertiesFormat.LoadBalancingRules - newLbRules := append(oldLbRules[:index], oldLbRules[index+1:]...) - loadBalancer.LoadBalancerPropertiesFormat.LoadBalancingRules = &newLbRules + rules := props.LoadBalancingRules + if rules == nil { + return fmt.Errorf("Error updating Load Balancer Rules: props.LoadBalancingRules was nil") + } + lbRules := *rules - resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(d.Get("loadbalancer_id").(string)) + newRules := make([]network.LoadBalancingRule, 0) + for _, rule := range lbRules { + if rule.Name != nil && *rule.Name != name { + newRules = append(newRules, rule) + } + } + loadBalancer.LoadBalancerPropertiesFormat.LoadBalancingRules = &newRules + + resGroup, loadBalancerName, err := resourceGroupAndLBNameFromId(loadBalancerID) if err != nil { return fmt.Errorf("Error Getting Load Balancer Name and Group:: %+v", err) } future, err := client.CreateOrUpdate(ctx, resGroup, loadBalancerName, *loadBalancer) if err != nil { - return fmt.Errorf("Error Creating/Updating Load Balancer %q (Resource Group %q): %+v", loadBalancerName, resGroup, err) + return fmt.Errorf("Error Updating Load Balancer %q (Resource Group %q): %+v", loadBalancerName, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for completion of Load Balancer %q (Resource Group %q): %+v", loadBalancerName, resGroup, err) } @@ -318,7 +372,6 @@ func resourceArmLoadBalancerRuleDelete(d *schema.ResourceData, meta interface{}) } func expandAzureRmLoadBalancerRule(d *schema.ResourceData, lb *network.LoadBalancer) (*network.LoadBalancingRule, error) { - properties := network.LoadBalancingRulePropertiesFormat{ Protocol: network.TransportProtocol(d.Get("protocol").(string)), FrontendPort: utils.Int32(int32(d.Get("frontend_port").(int))), diff --git a/azurerm/resource_arm_loadbalancer_rule_test.go b/azurerm/resource_arm_loadbalancer_rule_test.go index b325b21dc653..5eae0cf3a0c6 100644 --- a/azurerm/resource_arm_loadbalancer_rule_test.go +++ b/azurerm/resource_arm_loadbalancer_rule_test.go @@ -102,6 +102,31 @@ func TestAccAzureRMLoadBalancerRule_basic(t *testing.T) { }) } +func TestAccAzureRMLoadBalancerRule_requiresImport(t *testing.T) { + var lb network.LoadBalancer + ri := acctest.RandInt() + location := testLocation() + lbRuleName := fmt.Sprintf("LbRule-%s", acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMLoadBalancerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMLoadBalancerRule_basic(ri, lbRuleName, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMLoadBalancerExists("azurerm_lb.test", &lb), + ), + }, + { + Config: testAccAzureRMLoadBalancerRule_requiresImport(ri, lbRuleName, location), + ExpectError: testRequiresImportError("azurerm_lb_rule"), + }, + }, + }) +} + func TestAccAzureRMLoadBalancerRule_removal(t *testing.T) { var lb network.LoadBalancer ri := acctest.RandInt() @@ -263,7 +288,7 @@ func TestAccAzureRMLoadBalancerRule_disappears(t *testing.T) { func testCheckAzureRMLoadBalancerRuleExists(lbRuleName string, lb *network.LoadBalancer) resource.TestCheckFunc { return func(s *terraform.State) error { - _, _, exists := findLoadBalancerRuleByName(lb, lbRuleName) + _, exists := findAzureRMLoadBalancerRuleByName(lb, lbRuleName) if !exists { return fmt.Errorf("A Load Balancer Rule with name %q cannot be found.", lbRuleName) } @@ -274,7 +299,7 @@ func testCheckAzureRMLoadBalancerRuleExists(lbRuleName string, lb *network.LoadB func testCheckAzureRMLoadBalancerRuleNotExists(lbRuleName string, lb *network.LoadBalancer) resource.TestCheckFunc { return func(s *terraform.State) error { - _, _, exists := findLoadBalancerRuleByName(lb, lbRuleName) + _, exists := findAzureRMLoadBalancerRuleByName(lb, lbRuleName) if exists { return fmt.Errorf("A Load Balancer Rule with name %q has been found.", lbRuleName) } @@ -288,7 +313,7 @@ func testCheckAzureRMLoadBalancerRuleDisappears(ruleName string, lb *network.Loa client := testAccProvider.Meta().(*ArmClient).loadBalancerClient ctx := testAccProvider.Meta().(*ArmClient).StopContext - _, i, exists := findLoadBalancerRuleByName(lb, ruleName) + i, exists := findAzureRMLoadBalancerRuleByName(lb, ruleName) if !exists { return fmt.Errorf("A Rule with name %q cannot be found.", ruleName) } @@ -317,6 +342,20 @@ func testCheckAzureRMLoadBalancerRuleDisappears(ruleName string, lb *network.Loa } } +func findAzureRMLoadBalancerRuleByName(lb *network.LoadBalancer, name string) (int, bool) { + if lb == nil || lb.LoadBalancerPropertiesFormat == nil || lb.LoadBalancerPropertiesFormat.LoadBalancingRules == nil { + return -1, false + } + + for i, lbr := range *lb.LoadBalancerPropertiesFormat.LoadBalancingRules { + if lbr.Name != nil && *lbr.Name == name { + return i, true + } + } + + return -1, false +} + func testAccAzureRMLoadBalancerRule_basic(rInt int, lbRuleName string, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { @@ -355,6 +394,24 @@ resource "azurerm_lb_rule" "test" { `, rInt, location, rInt, rInt, rInt, lbRuleName, rInt) } +func testAccAzureRMLoadBalancerRule_requiresImport(rInt int, lbRuleName string, location string) string { + template := testAccAzureRMLoadBalancerRule_basic(rInt, lbRuleName, location) + return fmt.Sprintf(` +%s + +resource "azurerm_lb_rule" "import" { + name = "${azurerm_lb_rule.test.name}" + location = "${azurerm_lb_rule.test.location}" + resource_group_name = "${azurerm_lb_rule.test.resource_group_name}" + loadbalancer_id = "${azurerm_lb_rule.test.loadbalancer_id}" + frontend_ip_configuration_name = "${azurerm_lb_rule.test.frontend_ip_configuration_name}" + protocol = "${azurerm_lb_rule.test.protocol}" + frontend_port = "${azurerm_lb_rule.test.frontend_port}" + backend_port = "${azurerm_lb_rule.test.backend_port}" +} +`, template) +} + func testAccAzureRMLoadBalancerRule_removal(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_loadbalancer_test.go b/azurerm/resource_arm_loadbalancer_test.go index 8511ff3bcdb8..a0d081045cfb 100644 --- a/azurerm/resource_arm_loadbalancer_test.go +++ b/azurerm/resource_arm_loadbalancer_test.go @@ -11,42 +11,6 @@ import ( "github.com/hashicorp/terraform/terraform" ) -func TestResourceAzureRMLoadBalancerPrivateIpAddressAllocation_validation(t *testing.T) { - cases := []struct { - Value string - ErrCount int - }{ - { - Value: "Random", - ErrCount: 1, - }, - { - Value: "Static", - ErrCount: 0, - }, - { - Value: "Dynamic", - ErrCount: 0, - }, - { - Value: "STATIC", - ErrCount: 0, - }, - { - Value: "static", - ErrCount: 0, - }, - } - - for _, tc := range cases { - _, errors := validateLoadBalancerPrivateIpAddressAllocation(tc.Value, "azurerm_lb") - - if len(errors) != tc.ErrCount { - t.Fatalf("Expected the Azure RM Load Balancer private_ip_address_allocation to trigger a validation error") - } - } -} - func TestAccAzureRMLoadBalancer_basic(t *testing.T) { var lb network.LoadBalancer ri := acctest.RandInt() @@ -71,6 +35,30 @@ func TestAccAzureRMLoadBalancer_basic(t *testing.T) { }) } +func TestAccAzureRMLoadBalancer_requiresImport(t *testing.T) { + var lb network.LoadBalancer + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMLoadBalancerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMLoadBalancer_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMLoadBalancerExists("azurerm_lb.test", &lb), + ), + }, + { + Config: testAccAzureRMLoadBalancer_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_lb"), + }, + }, + }) +} + func TestAccAzureRMLoadBalancer_standard(t *testing.T) { var lb network.LoadBalancer ri := acctest.RandInt() @@ -263,7 +251,26 @@ resource "azurerm_lb" "test" { Purpose = "AcceptanceTests" } -}`, rInt, location, rInt) +} +`, rInt, location, rInt) +} + +func testAccAzureRMLoadBalancer_requiresImport(rInt int, location string) string { + template := testAccAzureRMLoadBalancer_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_lb" "import" { + name = "${azurerm_lb.test.name}" + location = "${azurerm_lb.test.location}" + resource_group_name = "${azurerm_lb.test.resource_group_name}" + + tags { + Environment = "production" + Purpose = "AcceptanceTests" + } +} +`, template) } func testAccAzureRMLoadBalancer_standard(rInt int, location string) string { diff --git a/azurerm/resource_arm_local_network_gateway.go b/azurerm/resource_arm_local_network_gateway.go index 84da0fd1f358..b5f7f3841255 100644 --- a/azurerm/resource_arm_local_network_gateway.go +++ b/azurerm/resource_arm_local_network_gateway.go @@ -1,23 +1,31 @@ package azurerm import ( + "context" "fmt" + "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) func resourceArmLocalNetworkGateway() *schema.Resource { return &schema.Resource{ - Create: resourceArmLocalNetworkGatewayCreate, + Create: resourceArmLocalNetworkGatewayCreateUpdate, Read: resourceArmLocalNetworkGatewayRead, - Update: resourceArmLocalNetworkGatewayCreate, + Update: resourceArmLocalNetworkGatewayCreateUpdate, Delete: resourceArmLocalNetworkGatewayDelete, 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": { @@ -73,13 +81,28 @@ func resourceArmLocalNetworkGateway() *schema.Resource { } } -func resourceArmLocalNetworkGatewayCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmLocalNetworkGatewayCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).localNetConnClient ctx := meta.(*ArmClient).StopContext name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_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 Local Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_local_network_gateway", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) ipAddress := d.Get("gateway_address").(string) addressSpaces := expandLocalNetworkGatewayAddressSpaces(d) @@ -109,7 +132,9 @@ func resourceArmLocalNetworkGatewayCreate(d *schema.ResourceData, meta interface return fmt.Errorf("Error creating Local Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for completion of Local Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) } @@ -189,7 +214,9 @@ func resourceArmLocalNetworkGatewayDelete(d *schema.ResourceData, meta interface return fmt.Errorf("Error issuing delete request for local network gateway %q (Resource Group %q): %+v", name, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if response.WasNotFound(future.Response()) { return nil diff --git a/azurerm/resource_arm_local_network_gateway_test.go b/azurerm/resource_arm_local_network_gateway_test.go index 6f09f359b1ed..6b25d5e3a4af 100644 --- a/azurerm/resource_arm_local_network_gateway_test.go +++ b/azurerm/resource_arm_local_network_gateway_test.go @@ -32,6 +32,30 @@ func TestAccAzureRMLocalNetworkGateway_basic(t *testing.T) { }) } +func TestAccAzureRMLocalNetworkGateway_requiresImport(t *testing.T) { + name := "azurerm_local_network_gateway.test" + rInt := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMLocalNetworkGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMLocalNetworkGatewayConfig_basic(rInt, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMLocalNetworkGatewayExists(name), + ), + }, + { + Config: testAccAzureRMLocalNetworkGatewayConfig_requiresImport(rInt, location), + ExpectError: testRequiresImportError("azurerm_local_network_gateway"), + }, + }, + }) +} + func TestAccAzureRMLocalNetworkGateway_disappears(t *testing.T) { name := "azurerm_local_network_gateway.test" rInt := acctest.RandInt() @@ -315,6 +339,21 @@ resource "azurerm_local_network_gateway" "test" { `, rInt, location, rInt) } +func testAccAzureRMLocalNetworkGatewayConfig_requiresImport(rInt int, location string) string { + template := testAccAzureRMLocalNetworkGatewayConfig_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_local_network_gateway" "import" { + name = "${azurerm_local_network_gateway.test.name}" + location = "${azurerm_local_network_gateway.test.location}" + resource_group_name = "${azurerm_local_network_gateway.test.resource_group_name}" + gateway_address = "${azurerm_local_network_gateway.test.gateway_address}" + address_space = "${azurerm_local_network_gateway.test.address_space}" +} +`, template) +} + func testAccAzureRMLocalNetworkGatewayConfig_tags(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_log_analytics_solution.go b/azurerm/resource_arm_log_analytics_solution.go index cba504fd2c26..1999d2a36715 100644 --- a/azurerm/resource_arm_log_analytics_solution.go +++ b/azurerm/resource_arm_log_analytics_solution.go @@ -1,12 +1,15 @@ package azurerm import ( + "context" "fmt" "log" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/operationsmanagement/mgmt/2015-11-01-preview/operationsmanagement" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" @@ -23,6 +26,11 @@ func resourceArmLogAnalyticsSolution() *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{ "solution_name": { @@ -89,12 +97,29 @@ func resourceArmLogAnalyticsSolutionCreateUpdate(d *schema.ResourceData, meta in // The resource requires both .name and .plan.name are set in the format // "SolutionName(WorkspaceName)". Feedback will be submitted to the OMS team as IMO this isn't ideal. - name := fmt.Sprintf("%s(%s)", d.Get("solution_name").(string), d.Get("workspace_name").(string)) + solutionName := d.Get("solution_name").(string) + workspaceName := d.Get("workspace_name").(string) + name := fmt.Sprintf("%s(%s)", solutionName, workspaceName) + resGroup := d.Get("resource_group_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 Log Analytics Solution %q (Workspace %q / Resource Group %q): %+v", solutionName, workspaceName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_log_analytics_solution", *resp.ID) + } + } + solutionPlan := expandAzureRmLogAnalyticsSolutionPlan(d) solutionPlan.Name = &name location := azureRMNormalizeLocation(d.Get("location").(string)) - resGroup := d.Get("resource_group_name").(string) workspaceID := d.Get("workspace_resource_id").(string) parameters := operationsmanagement.Solution{ @@ -111,7 +136,9 @@ func resourceArmLogAnalyticsSolutionCreateUpdate(d *schema.ResourceData, meta in return fmt.Errorf("Error creating/updating Log Analytics Solution %q (Workspace %q / Resource Group %q): %+v", name, workspaceID, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for the create/update of Log Analytics Solution %q (Workspace %q / Resource Group %q): %+v", name, workspaceID, resGroup, err) } @@ -200,7 +227,9 @@ func resourceArmLogAnalyticsSolutionDelete(d *schema.ResourceData, meta interfac return fmt.Errorf("Error deleting Log Analytics Solution %q (Resource Group %q): %+v", name, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if !response.WasNotFound(future.Response()) { return fmt.Errorf("Error waiting for deletion of Log Analytics Solution %q (Resource Group %q): %+v", name, resGroup, err) diff --git a/azurerm/resource_arm_log_analytics_solution_test.go b/azurerm/resource_arm_log_analytics_solution_test.go index f908ae3a953c..387beb87eb7a 100644 --- a/azurerm/resource_arm_log_analytics_solution_test.go +++ b/azurerm/resource_arm_log_analytics_solution_test.go @@ -28,6 +28,28 @@ func TestAccAzureRMLogAnalyticsSolution_basicContainerMonitoring(t *testing.T) { }) } +func TestAccAzureRMLogAnalyticsSolution_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMLogAnalyticsSolutionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMLogAnalyticsSolution_containerMonitoring(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMLogAnalyticsSolutionExists("azurerm_log_analytics_solution.test"), + ), + }, + { + Config: testAccAzureRMLogAnalyticsSolution_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_log_analytics_solution"), + }, + }, + }) +} + func TestAccAzureRMLogAnalyticsSolution_basicSecurity(t *testing.T) { ri := acctest.RandInt() config := testAccAzureRMLogAnalyticsSolution_security(ri, testLocation()) @@ -131,6 +153,26 @@ resource "azurerm_log_analytics_solution" "test" { `, rInt, location, rInt) } +func testAccAzureRMLogAnalyticsSolution_requiresImport(rInt int, location string) string { + template := testAccAzureRMLogAnalyticsSolution_containerMonitoring(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_log_analytics_solution" "import" { + solution_name = "${azurerm_log_analytics_solution.test.name}" + location = "${azurerm_log_analytics_solution.test.location}" + resource_group_name = "${azurerm_log_analytics_solution.test.resource_group_name}" + workspace_resource_id = "${azurerm_log_analytics_solution.test.workspace_resource_id}" + workspace_name = "${azurerm_log_analytics_solution.test.workspace_name}" + + plan { + publisher = "Microsoft" + product = "OMSGallery/Containers" + } +} +`, template) +} + func testAccAzureRMLogAnalyticsSolution_security(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_log_analytics_workspace.go b/azurerm/resource_arm_log_analytics_workspace.go index 3ea74c3e4fad..d08c154d5e29 100644 --- a/azurerm/resource_arm_log_analytics_workspace.go +++ b/azurerm/resource_arm_log_analytics_workspace.go @@ -1,13 +1,16 @@ package azurerm import ( + "context" "fmt" "log" "regexp" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/operationalinsights/mgmt/2015-11-01-preview/operationalinsights" "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 resourceArmLogAnalyticsWorkspace() *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": { @@ -86,19 +94,32 @@ func resourceArmLogAnalyticsWorkspace() *schema.Resource { func resourceArmLogAnalyticsWorkspaceCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).workspacesClient ctx := meta.(*ArmClient).StopContext - log.Printf("[INFO] preparing arguments for AzureRM Log Analytics workspace creation.") + log.Printf("[INFO] preparing arguments for AzureRM Log Analytics Workspace creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_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 Log Analytics Workspace %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_log_analytics_workspace", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) skuName := d.Get("sku").(string) sku := &operationalinsights.Sku{ Name: operationalinsights.SkuNameEnum(skuName), } retentionInDays := int32(d.Get("retention_in_days").(int)) - tags := d.Get("tags").(map[string]interface{}) parameters := operationalinsights.Workspace{ @@ -116,7 +137,9 @@ func resourceArmLogAnalyticsWorkspaceCreateUpdate(d *schema.ResourceData, meta i return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -190,7 +213,9 @@ func resourceArmLogAnalyticsWorkspaceDelete(d *schema.ResourceData, meta interfa resGroup := id.ResourceGroup name := id.Path["workspaces"] - 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) { diff --git a/azurerm/resource_arm_log_analytics_workspace_test.go b/azurerm/resource_arm_log_analytics_workspace_test.go index 8cd26c17a09f..cb997267282f 100644 --- a/azurerm/resource_arm_log_analytics_workspace_test.go +++ b/azurerm/resource_arm_log_analytics_workspace_test.go @@ -70,6 +70,29 @@ func TestAccAzureRMLogAnalyticsWorkspace_requiredOnly(t *testing.T) { }) } +func TestAccAzureRMLogAnalyticsWorkspace_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMLogAnalyticsWorkspaceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMLogAnalyticsWorkspace_requiredOnly(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMLogAnalyticsWorkspaceExists("azurerm_log_analytics_workspace.test"), + ), + }, + { + Config: testAccAzureRMLogAnalyticsWorkspace_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_log_analytics_workspace"), + }, + }, + }) +} + func TestAccAzureRMLogAnalyticsWorkspace_retentionInDaysComplete(t *testing.T) { ri := acctest.RandInt() config := testAccAzureRMLogAnalyticsWorkspace_retentionInDaysComplete(ri, testLocation()) @@ -160,6 +183,20 @@ resource "azurerm_log_analytics_workspace" "test" { `, rInt, location, rInt) } +func testAccAzureRMLogAnalyticsWorkspace_requiresImport(rInt int, location string) string { + template := testAccAzureRMLogAnalyticsWorkspace_requiredOnly(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_log_analytics_workspace" "import" { + name = "${azurerm_log_analytics_workspace.test.name}" + location = "${azurerm_log_analytics_workspace.test.location}" + resource_group_name = "${azurerm_log_analytics_workspace.test.resource_group_name}" + sku = "${azurerm_log_analytics_workspace.test.sku}" +} +`, template) +} + func testAccAzureRMLogAnalyticsWorkspace_retentionInDaysComplete(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_logic_app_action_custom.go b/azurerm/resource_arm_logic_app_action_custom.go index fdb1ceb79d88..cd32dfdff888 100644 --- a/azurerm/resource_arm_logic_app_action_custom.go +++ b/azurerm/resource_arm_logic_app_action_custom.go @@ -3,6 +3,7 @@ package azurerm import ( "fmt" "log" + "time" "encoding/json" @@ -21,6 +22,11 @@ func resourceArmLogicAppActionCustom() *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": { @@ -57,7 +63,7 @@ func resourceArmLogicAppActionCustomCreateUpdate(d *schema.ResourceData, meta in return fmt.Errorf("Error unmarshalling JSON for Custom Action %q: %+v", name, err) } - err = resourceLogicAppActionUpdate(d, meta, logicAppId, name, body) + err = resourceLogicAppActionUpdate(d, meta, logicAppId, name, body, "azurerm_logic_app_action_custom") if err != nil { return err } diff --git a/azurerm/resource_arm_logic_app_action_custom_test.go b/azurerm/resource_arm_logic_app_action_custom_test.go index 3cb4231a1d30..d7345ed166c5 100644 --- a/azurerm/resource_arm_logic_app_action_custom_test.go +++ b/azurerm/resource_arm_logic_app_action_custom_test.go @@ -27,6 +27,29 @@ func TestAccAzureRMLogicAppActionCustom_basic(t *testing.T) { }) } +func TestAccAzureRMLogicAppActionCustom_requiresImport(t *testing.T) { + resourceName := "azurerm_logic_app_action_custom.test" + ri := acctest.RandInt() + location := testLocation() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMLogicAppWorkflowDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMLogicAppActionCustom_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMLogicAppActionExists(resourceName), + ), + }, + { + Config: testAccAzureRMLogicAppActionCustom_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_logic_app_action_custom"), + }, + }, + }) +} + func testAccAzureRMLogicAppActionCustom_basic(rInt int, location string) string { template := testAccAzureRMLogicAppActionCustom_template(rInt, location) return fmt.Sprintf(` @@ -55,6 +78,20 @@ BODY `, template, rInt) } +func testAccAzureRMLogicAppActionCustom_requiresImport(rInt int, location string) string { + template := testAccAzureRMLogicAppActionCustom_basic(rInt, location) + + return fmt.Sprintf(` +%s + +resource "azurerm_logic_app_action_custom" "import" { + name = "${azurerm_logic_app_action_custom.test.id}" + logic_app_id = "${azurerm_logic_app_action_custom.test.logic_app_id}" + body = "${azurerm_logic_app_action_custom.test.body}" +} +`, template) +} + func testAccAzureRMLogicAppActionCustom_template(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_logic_app_action_http.go b/azurerm/resource_arm_logic_app_action_http.go index 6b21a07ba44e..93aae9db7580 100644 --- a/azurerm/resource_arm_logic_app_action_http.go +++ b/azurerm/resource_arm_logic_app_action_http.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "strings" + "time" "net/http" @@ -21,6 +22,11 @@ func resourceArmLogicAppActionHTTP() *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": { @@ -90,7 +96,7 @@ func resourceArmLogicAppActionHTTPCreateUpdate(d *schema.ResourceData, meta inte logicAppId := d.Get("logic_app_id").(string) name := d.Get("name").(string) - err = resourceLogicAppActionUpdate(d, meta, logicAppId, name, action) + err = resourceLogicAppActionUpdate(d, meta, logicAppId, name, action, "azurerm_logic_app_action_http") if err != nil { return err } diff --git a/azurerm/resource_arm_logic_app_action_http_test.go b/azurerm/resource_arm_logic_app_action_http_test.go index d0c7c567da53..7b9d1f6b61d3 100644 --- a/azurerm/resource_arm_logic_app_action_http_test.go +++ b/azurerm/resource_arm_logic_app_action_http_test.go @@ -27,6 +27,29 @@ func TestAccAzureRMLogicAppActionHttp_basic(t *testing.T) { }) } +func TestAccAzureRMLogicAppActionHttp_requiresImport(t *testing.T) { + resourceName := "azurerm_logic_app_action_http.test" + ri := acctest.RandInt() + location := testLocation() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMLogicAppWorkflowDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMLogicAppActionHttp_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMLogicAppActionExists(resourceName), + ), + }, + { + Config: testAccAzureRMLogicAppActionHttp_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_logic_app_action_http"), + }, + }, + }) +} + func TestAccAzureRMLogicAppActionHttp_headers(t *testing.T) { resourceName := "azurerm_logic_app_action_http.test" ri := acctest.RandInt() @@ -90,6 +113,20 @@ resource "azurerm_logic_app_action_http" "test" { `, template, rInt) } +func testAccAzureRMLogicAppActionHttp_requiresImport(rInt int, location string) string { + template := testAccAzureRMLogicAppActionHttp_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_logic_app_action_http" "import" { + name = "${azurerm_logic_app_action_http.test.name}" + logic_app_id = "${azurerm_logic_app_action_http.test.logic_app_id}" + method = "${azurerm_logic_app_action_http.test.method}" + uri = "${azurerm_logic_app_action_http.test.uri}" +} +`, template) +} + func testAccAzureRMLogicAppActionHttp_headers(rInt int, location string) string { template := testAccAzureRMLogicAppActionHttp_template(rInt, location) return fmt.Sprintf(` diff --git a/azurerm/resource_arm_logic_app_trigger_custom.go b/azurerm/resource_arm_logic_app_trigger_custom.go index 49351214ba01..b0b89cda7f5f 100644 --- a/azurerm/resource_arm_logic_app_trigger_custom.go +++ b/azurerm/resource_arm_logic_app_trigger_custom.go @@ -3,6 +3,7 @@ package azurerm import ( "fmt" "log" + "time" "encoding/json" @@ -21,6 +22,11 @@ func resourceArmLogicAppTriggerCustom() *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": { @@ -57,7 +63,7 @@ func resourceArmLogicAppTriggerCustomCreateUpdate(d *schema.ResourceData, meta i return fmt.Errorf("Error unmarshalling JSON for Custom Trigger %q: %+v", name, err) } - err = resourceLogicAppTriggerUpdate(d, meta, logicAppId, name, body) + err = resourceLogicAppTriggerUpdate(d, meta, logicAppId, name, body, "azurerm_logic_app_trigger_custom") if err != nil { return err } diff --git a/azurerm/resource_arm_logic_app_trigger_custom_test.go b/azurerm/resource_arm_logic_app_trigger_custom_test.go index 8023f7b1b058..975d5d61af8e 100644 --- a/azurerm/resource_arm_logic_app_trigger_custom_test.go +++ b/azurerm/resource_arm_logic_app_trigger_custom_test.go @@ -27,6 +27,29 @@ func TestAccAzureRMLogicAppTriggerCustom_basic(t *testing.T) { }) } +func TestAccAzureRMLogicAppTriggerCustom_requiresImport(t *testing.T) { + resourceName := "azurerm_logic_app_trigger_custom.test" + ri := acctest.RandInt() + location := testLocation() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMLogicAppWorkflowDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMLogicAppTriggerCustom_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMLogicAppTriggerExists(resourceName), + ), + }, + { + Config: testAccAzureRMLogicAppTriggerCustom_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_logic_app_trigger_custom"), + }, + }, + }) +} + func testAccAzureRMLogicAppTriggerCustom_basic(rInt int, location string) string { template := testAccAzureRMLogicAppTriggerCustom_template(rInt, location) return fmt.Sprintf(` @@ -48,6 +71,19 @@ BODY `, template, rInt) } +func testAccAzureRMLogicAppTriggerCustom_requiresImport(rInt int, location string) string { + template := testAccAzureRMLogicAppTriggerCustom_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_logic_app_trigger_custom" "import" { + name = "${azurerm_logic_app_trigger_custom.test.name}" + logic_app_id = "${azurerm_logic_app_trigger_custom.test.logic_app_id}" + body = "${azurerm_logic_app_trigger_custom.test.body}" +} +`, template) +} + func testAccAzureRMLogicAppTriggerCustom_template(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_logic_app_trigger_http_request.go b/azurerm/resource_arm_logic_app_trigger_http_request.go index bcd7346f2c41..44a16218ae97 100644 --- a/azurerm/resource_arm_logic_app_trigger_http_request.go +++ b/azurerm/resource_arm_logic_app_trigger_http_request.go @@ -6,6 +6,7 @@ import ( "log" "net/http" "regexp" + "time" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/structure" @@ -22,6 +23,11 @@ func resourceArmLogicAppTriggerHttpRequest() *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), + }, CustomizeDiff: func(diff *schema.ResourceDiff, v interface{}) error { @@ -106,7 +112,7 @@ func resourceArmLogicAppTriggerHttpRequestCreateUpdate(d *schema.ResourceData, m logicAppId := d.Get("logic_app_id").(string) name := d.Get("name").(string) - err = resourceLogicAppTriggerUpdate(d, meta, logicAppId, name, trigger) + err = resourceLogicAppTriggerUpdate(d, meta, logicAppId, name, trigger, "azurerm_logic_app_trigger_http_request") if err != nil { return err } diff --git a/azurerm/resource_arm_logic_app_trigger_http_request_test.go b/azurerm/resource_arm_logic_app_trigger_http_request_test.go index 981b14323b6d..260c9fa72968 100644 --- a/azurerm/resource_arm_logic_app_trigger_http_request_test.go +++ b/azurerm/resource_arm_logic_app_trigger_http_request_test.go @@ -28,6 +28,30 @@ func TestAccAzureRMLogicAppTriggerHttpRequest_basic(t *testing.T) { }) } +func TestAccAzureRMLogicAppTriggerHttpRequest_requiresImport(t *testing.T) { + resourceName := "azurerm_logic_app_trigger_http_request.test" + ri := acctest.RandInt() + location := testLocation() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMLogicAppWorkflowDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMLogicAppTriggerHttpRequest_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMLogicAppTriggerExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "schema", "{}"), + ), + }, + { + Config: testAccAzureRMLogicAppTriggerHttpRequest_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_logic_app_trigger_http_request"), + }, + }, + }) +} + func TestAccAzureRMLogicAppTriggerHttpRequest_fullSchema(t *testing.T) { resourceName := "azurerm_logic_app_trigger_http_request.test" ri := acctest.RandInt() @@ -132,6 +156,19 @@ resource "azurerm_logic_app_trigger_http_request" "test" { `, template) } +func testAccAzureRMLogicAppTriggerHttpRequest_requiresImport(rInt int, location string) string { + template := testAccAzureRMLogicAppTriggerHttpRequest_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_logic_app_trigger_http_request" "import" { + name = "${azurerm_logic_app_trigger_http_request.test.name}" + logic_app_id = "${azurerm_logic_app_trigger_http_request.test.logic_app_id}" + schema = "${azurerm_logic_app_trigger_http_request.test.schema}" +} +`, template) +} + func testAccAzureRMLogicAppTriggerHttpRequest_fullSchema(rInt int, location string) string { template := testAccAzureRMLogicAppTriggerHttpRequest_template(rInt, location) return fmt.Sprintf(` diff --git a/azurerm/resource_arm_logic_app_trigger_recurrence.go b/azurerm/resource_arm_logic_app_trigger_recurrence.go index 8e1e8ad4d5df..a2c5f29f18a5 100644 --- a/azurerm/resource_arm_logic_app_trigger_recurrence.go +++ b/azurerm/resource_arm_logic_app_trigger_recurrence.go @@ -3,6 +3,7 @@ package azurerm import ( "fmt" "log" + "time" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" @@ -18,6 +19,11 @@ func resourceArmLogicAppTriggerRecurrence() *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": { @@ -66,7 +72,7 @@ func resourceArmLogicAppTriggerRecurrenceCreateUpdate(d *schema.ResourceData, me logicAppId := d.Get("logic_app_id").(string) name := d.Get("name").(string) - err := resourceLogicAppTriggerUpdate(d, meta, logicAppId, name, trigger) + err := resourceLogicAppTriggerUpdate(d, meta, logicAppId, name, trigger, "azurerm_logic_app_trigger_recurrence") if err != nil { return err } diff --git a/azurerm/resource_arm_logic_app_trigger_recurrence_test.go b/azurerm/resource_arm_logic_app_trigger_recurrence_test.go index 4a8146d2dc1e..eeb5499392ed 100644 --- a/azurerm/resource_arm_logic_app_trigger_recurrence_test.go +++ b/azurerm/resource_arm_logic_app_trigger_recurrence_test.go @@ -29,6 +29,29 @@ func TestAccAzureRMLogicAppTriggerRecurrence_month(t *testing.T) { }) } +func TestAccAzureRMLogicAppTriggerRecurrence_requiresImport(t *testing.T) { + resourceName := "azurerm_logic_app_trigger_recurrence.test" + ri := acctest.RandInt() + location := testLocation() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMLogicAppWorkflowDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMLogicAppTriggerRecurrence_basic(ri, location, "Month", 1), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMLogicAppTriggerExists(resourceName), + ), + }, + { + Config: testAccAzureRMLogicAppTriggerRecurrence_requiresImport(ri, location, "Month", 1), + ExpectError: testRequiresImportError("azurerm_logic_app_trigger_recurrence"), + }, + }, + }) +} + func TestAccAzureRMLogicAppTriggerRecurrence_week(t *testing.T) { resourceName := "azurerm_logic_app_trigger_recurrence.test" ri := acctest.RandInt() @@ -184,3 +207,17 @@ resource "azurerm_logic_app_trigger_recurrence" "test" { } `, rInt, location, rInt, frequency, interval) } + +func testAccAzureRMLogicAppTriggerRecurrence_requiresImport(rInt int, location, frequency string, interval int) string { + template := testAccAzureRMLogicAppTriggerRecurrence_basic(rInt, location, frequency, interval) + return fmt.Sprintf(` +%s + +resource "azurerm_logic_app_trigger_recurrence" "import" { + name = "${azurerm_logic_app_trigger_recurrence.test.name}" + logic_app_id = "${azurerm_logic_app_trigger_recurrence.test.logic_app_id}" + frequency = "${azurerm_logic_app_trigger_recurrence.test.frequency}" + interval = "${azurerm_logic_app_trigger_recurrence.test.interval}" +} +`, template) +} diff --git a/azurerm/resource_arm_logic_app_workflow.go b/azurerm/resource_arm_logic_app_workflow.go index 0f7bd038dad4..01042eb7f5f0 100644 --- a/azurerm/resource_arm_logic_app_workflow.go +++ b/azurerm/resource_arm_logic_app_workflow.go @@ -1,11 +1,14 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/logic/mgmt/2016-06-01/logic" "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" ) @@ -20,6 +23,11 @@ func resourceArmLogicAppWorkflow() *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": { @@ -70,6 +78,21 @@ func resourceArmLogicAppWorkflowCreate(d *schema.ResourceData, meta interface{}) name := d.Get("name").(string) resourceGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Logic App Workflow %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_logic_app_workflow", *resp.ID) + } + } + location := azureRMNormalizeLocation(d.Get("location").(string)) parameters := expandLogicAppWorkflowParameters(d.Get("parameters").(map[string]interface{})) @@ -91,7 +114,9 @@ func resourceArmLogicAppWorkflowCreate(d *schema.ResourceData, meta interface{}) Tags: expandTags(tags), } - _, err := client.CreateOrUpdate(ctx, resourceGroup, name, properties) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, resourceGroup, name, properties) if err != nil { return fmt.Errorf("[ERROR] Error creating Logic App Workflow %q (Resource Group %q): %+v", name, resourceGroup, err) } @@ -152,7 +177,9 @@ func resourceArmLogicAppWorkflowUpdate(d *schema.ResourceData, meta interface{}) Tags: expandTags(tags), } - _, err = client.CreateOrUpdate(ctx, resourceGroup, name, properties) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutUpdate)) + defer cancel() + _, err = client.CreateOrUpdate(waitCtx, resourceGroup, name, properties) if err != nil { return fmt.Errorf("Error updating Logic App Workspace %q (Resource Group %q): %+v", name, resourceGroup, err) } @@ -226,7 +253,9 @@ func resourceArmLogicAppWorkflowDelete(d *schema.ResourceData, meta interface{}) azureRMLockByName(name, logicAppResourceName) defer azureRMUnlockByName(name, logicAppResourceName) - resp, err := client.Delete(ctx, resourceGroup, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resourceGroup, name) if err != nil { if utils.ResponseWasNotFound(resp) { return nil diff --git a/azurerm/resource_arm_logic_app_workflow_test.go b/azurerm/resource_arm_logic_app_workflow_test.go index 4976f40949c8..4bf9f92c0dcc 100644 --- a/azurerm/resource_arm_logic_app_workflow_test.go +++ b/azurerm/resource_arm_logic_app_workflow_test.go @@ -31,6 +31,29 @@ func TestAccAzureRMLogicAppWorkflow_empty(t *testing.T) { }) } +func TestAccAzureRMLogicAppWorkflow_requiresImport(t *testing.T) { + resourceName := "azurerm_logic_app_workflow.test" + ri := acctest.RandInt() + location := testLocation() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMLogicAppWorkflowDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMLogicAppWorkflow_empty(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMLogicAppWorkflowExists(resourceName), + ), + }, + { + Config: testAccAzureRMLogicAppWorkflow_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_logic_app_workflow"), + }, + }, + }) +} + func TestAccAzureRMLogicAppWorkflow_tags(t *testing.T) { resourceName := "azurerm_logic_app_workflow.test" ri := acctest.RandInt() @@ -131,6 +154,19 @@ resource "azurerm_logic_app_workflow" "test" { `, rInt, location, rInt) } +func testAccAzureRMLogicAppWorkflow_requiresImport(rInt int, location string) string { + template := testAccAzureRMLogicAppWorkflow_empty(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_logic_app_workflow" "import" { + name = "${azurerm_logic_app_workflow.test.name}" + location = "${azurerm_logic_app_workflow.test.location}" + resource_group_name = "${azurerm_logic_app_workflow.test.resource_group_name}" +} +`, template) +} + func testAccAzureRMLogicAppWorkflow_tags(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_managed_disk.go b/azurerm/resource_arm_managed_disk.go index 9449f83474f6..db65fe7dd204 100644 --- a/azurerm/resource_arm_managed_disk.go +++ b/azurerm/resource_arm_managed_disk.go @@ -1,26 +1,34 @@ package azurerm import ( + "context" "fmt" "log" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-06-01/compute" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) func resourceArmManagedDisk() *schema.Resource { return &schema.Resource{ - Create: resourceArmManagedDiskCreate, + Create: resourceArmManagedDiskCreateUpdate, Read: resourceArmManagedDiskRead, - Update: resourceArmManagedDiskCreate, + Update: resourceArmManagedDiskCreateUpdate, Delete: resourceArmManagedDiskDelete, 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": { @@ -109,15 +117,30 @@ func validateDiskSizeGB(v interface{}, k string) (ws []string, errors []error) { return } -func resourceArmManagedDiskCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmManagedDiskCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).diskClient ctx := meta.(*ArmClient).StopContext log.Printf("[INFO] preparing arguments for Azure ARM Managed Disk creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_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 Managed Disk %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_managed_disk", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) storageAccountType := d.Get("storage_account_type").(string) osType := d.Get("os_type").(string) tags := d.Get("tags").(map[string]interface{}) @@ -189,7 +212,9 @@ func resourceArmManagedDiskCreate(d *schema.ResourceData, meta interface{}) erro return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -282,7 +307,9 @@ func resourceArmManagedDiskDelete(d *schema.ResourceData, meta interface{}) erro } } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if !response.WasNotFound(future.Response()) { return err diff --git a/azurerm/resource_arm_managed_disk_test.go b/azurerm/resource_arm_managed_disk_test.go index 091f45ded2f9..1a0e86aeef17 100644 --- a/azurerm/resource_arm_managed_disk_test.go +++ b/azurerm/resource_arm_managed_disk_test.go @@ -30,6 +30,29 @@ func TestAccAzureRMManagedDisk_empty(t *testing.T) { }) } +func TestAccAzureRMManagedDisk_requiresImport(t *testing.T) { + var d compute.Disk + ri := acctest.RandInt() + location := testLocation() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMManagedDiskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMManagedDisk_empty(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMManagedDiskExists("azurerm_managed_disk.test", &d, true), + ), + }, + { + Config: testAccAzureRMManagedDisk_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_managed_disk"), + }, + }, + }) +} + func TestAccAzureRMManagedDisk_zeroGbFromPlatformImage(t *testing.T) { var d compute.Disk ri := acctest.RandInt() @@ -323,6 +346,27 @@ resource "azurerm_managed_disk" "test" { `, rInt, location, rInt) } +func testAccAzureRMManagedDisk_requiresImport(rInt int, location string) string { + template := testAccAzureRMManagedDisk_empty(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_managed_disk" "import" { + name = "${azurerm_managed_disk.test.name}" + location = "${azurerm_managed_disk.test.location}" + resource_group_name = "${azurerm_managed_disk.test.resource_group_name}" + storage_account_type = "Standard_LRS" + create_option = "Empty" + disk_size_gb = "1" + + tags { + environment = "acctest" + cost-center = "ops" + } +} +`, template) +} + func testAccAzureRMManagedDisk_empty_withZone(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_management_group.go b/azurerm/resource_arm_management_group.go index da1361161683..18b12c2dd8e5 100644 --- a/azurerm/resource_arm_management_group.go +++ b/azurerm/resource_arm_management_group.go @@ -1,14 +1,17 @@ package azurerm import ( + "context" "fmt" "log" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2018-03-01-preview/management" "github.com/google/uuid" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -23,6 +26,11 @@ func resourceArmManagementGroup() *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{ "group_id": { @@ -63,7 +71,23 @@ func resourceArmManagementGroupCreateUpdate(d *schema.ResourceData, meta interfa groupId := d.Get("group_id").(string) if groupId == "" { groupId = uuid.New().String() + } else { + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + recurse := true + resp, err := client.Get(ctx, groupId, "", &recurse, "", managementGroupCacheControl) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Management Group %q: %+v", groupId, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_management_group", *resp.ID) + } + } } + parentManagementGroupId := d.Get("parent_management_group_id").(string) if parentManagementGroupId == "" { parentManagementGroupId = fmt.Sprintf("/providers/Microsoft.Management/managementGroups/%s", armTenantID) @@ -92,7 +116,9 @@ func resourceArmManagementGroupCreateUpdate(d *schema.ResourceData, meta interfa return fmt.Errorf("Error creating Management Group %q: %+v", groupId, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for creation of Management Group %q: %+v", groupId, err) } @@ -209,6 +235,9 @@ func resourceArmManagementGroupDelete(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error retrieving Management Group %q: %+v", id.groupId, err) } + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + // before deleting a management group, return any subscriptions to the root management group if props := group.Properties; props != nil { if children := props.Children; children != nil { @@ -220,7 +249,7 @@ func resourceArmManagementGroupDelete(d *schema.ResourceData, meta interface{}) subscriptionId := *v.ID log.Printf("[DEBUG] De-associating Subscription %q from Management Group %q..", subscriptionId, id.groupId) // NOTE: whilst this says `Delete` it's actually `Deassociate` - which is /really/ helpful - deleteResp, err := subscriptionsClient.Delete(ctx, id.groupId, subscriptionId, managementGroupCacheControl) + deleteResp, err := subscriptionsClient.Delete(waitCtx, id.groupId, subscriptionId, managementGroupCacheControl) if err != nil { if !response.WasNotFound(deleteResp.Response) { return fmt.Errorf("Error de-associating Subscription %q from Management Group %q: %+v", subscriptionId, id.groupId, err) @@ -235,7 +264,7 @@ func resourceArmManagementGroupDelete(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error deleting Management Group %q: %+v", id.groupId, err) } - err = resp.WaitForCompletionRef(ctx, client.Client) + err = resp.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for the deletion of Management Group %q: %+v", id.groupId, err) } diff --git a/azurerm/resource_arm_management_group_test.go b/azurerm/resource_arm_management_group_test.go index 48c3966fb1f4..1be2a800eb3b 100644 --- a/azurerm/resource_arm_management_group_test.go +++ b/azurerm/resource_arm_management_group_test.go @@ -34,6 +34,28 @@ func TestAccAzureRMManagementGroup_basic(t *testing.T) { }) } +func TestAccAzureRMManagementGroup_requiresImport(t *testing.T) { + resourceName := "azurerm_management_group.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMManagementGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAzureRMManagementGroup_basic(), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMManagementGroupExists(resourceName), + ), + }, + { + Config: testAzureRMManagementGroup_requiresImport(), + ExpectError: testRequiresImportError("azurerm_management_group"), + }, + }, + }) +} + func TestAccAzureRMManagementGroup_nested(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -241,6 +263,18 @@ resource "azurerm_management_group" "test" { `) } +func testAzureRMManagementGroup_requiresImport() string { + template := testAzureRMManagementGroup_basic() + return fmt.Sprintf(` + +%s + +resource "azurerm_management_group" "import" { + group_id = "${azurerm_management_group.test.group_id}" +} +`, template) +} + func testAzureRMManagementGroup_nested() string { return fmt.Sprintf(` resource "azurerm_management_group" "parent" { diff --git a/azurerm/resource_arm_management_lock.go b/azurerm/resource_arm_management_lock.go index 1354459085ff..8b050478c3e1 100644 --- a/azurerm/resource_arm_management_lock.go +++ b/azurerm/resource_arm_management_lock.go @@ -1,25 +1,32 @@ package azurerm import ( + "context" "fmt" "log" "regexp" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2016-09-01/locks" "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" ) func resourceArmManagementLock() *schema.Resource { return &schema.Resource{ - Create: resourceArmManagementLockCreateUpdate, + Create: resourceArmManagementLockCreate, Read: resourceArmManagementLockRead, Delete: resourceArmManagementLockDelete, 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{ "name": { @@ -55,13 +62,26 @@ func resourceArmManagementLock() *schema.Resource { } } -func resourceArmManagementLockCreateUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceArmManagementLockCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).managementLocksClient ctx := meta.(*ArmClient).StopContext log.Printf("[INFO] preparing arguments for AzureRM Management Lock creation.") name := d.Get("name").(string) scope := d.Get("scope").(string) + + // first check if there's one in this subscription requiring import + resp, err := client.GetByScope(ctx, scope, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Management Lock %q (Scope %q): %+v", name, scope, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_management_lock", *resp.ID) + } + lockLevel := d.Get("lock_level").(string) notes := d.Get("notes").(string) @@ -72,7 +92,9 @@ func resourceArmManagementLockCreateUpdate(d *schema.ResourceData, meta interfac }, } - _, err := client.CreateOrUpdateByScope(ctx, scope, name, lock) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + _, err = client.CreateOrUpdateByScope(waitCtx, scope, name, lock) if err != nil { return err } @@ -128,7 +150,9 @@ func resourceArmManagementLockDelete(d *schema.ResourceData, meta interface{}) e return err } - resp, err := client.DeleteByScope(ctx, id.Scope, id.Name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.DeleteByScope(waitCtx, id.Scope, id.Name) if err != nil { if utils.ResponseWasNotFound(resp) { return nil diff --git a/azurerm/resource_arm_management_lock_test.go b/azurerm/resource_arm_management_lock_test.go index d76fe9e6fccc..48447d15788c 100644 --- a/azurerm/resource_arm_management_lock_test.go +++ b/azurerm/resource_arm_management_lock_test.go @@ -60,6 +60,30 @@ func TestAccAzureRMManagementLock_resourceGroupReadOnlyBasic(t *testing.T) { }) } +func TestAccAzureRMManagementLock_requiresImport(t *testing.T) { + resourceName := "azurerm_management_lock.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMManagementLockDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMManagementLock_resourceGroupReadOnlyBasic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMManagementLockExists(resourceName), + ), + }, + { + Config: testAccAzureRMManagementLock_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_management_lock"), + }, + }, + }) +} + func TestAccAzureRMManagementLock_resourceGroupReadOnlyComplete(t *testing.T) { resourceName := "azurerm_management_lock.test" ri := acctest.RandInt() @@ -283,6 +307,19 @@ resource "azurerm_management_lock" "test" { `, rInt, location, rInt) } +func testAccAzureRMManagementLock_requiresImport(rInt int, location string) string { + template := testAccAzureRMManagementLock_resourceGroupReadOnlyBasic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_management_lock" "import" { + name = "${azurerm_management_lock.test.name}" + scope = "${azurerm_management_lock.test.scope}" + lock_level = "${azurerm_management_lock.test.lock_level}" +} +`, template) +} + func testAccAzureRMManagementLock_resourceGroupReadOnlyComplete(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_metric_alertrule.go b/azurerm/resource_arm_metric_alertrule.go index a139cf07c3e1..0bec753aab8b 100644 --- a/azurerm/resource_arm_metric_alertrule.go +++ b/azurerm/resource_arm_metric_alertrule.go @@ -1,25 +1,34 @@ package azurerm import ( + "context" "fmt" "log" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/monitor/mgmt/2018-03-01/insights" "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" ) +// TODO: consider renaming this `azurerm_monitor_alert_rule` for consistency func resourceArmMetricAlertRule() *schema.Resource { return &schema.Resource{ - Create: resourceArmMetricAlertRuleCreateOrUpdate, + Create: resourceArmMetricAlertRuleCreateUpdate, Read: resourceArmMetricAlertRuleRead, - Update: resourceArmMetricAlertRuleCreateOrUpdate, + Update: resourceArmMetricAlertRuleCreateUpdate, Delete: resourceArmMetricAlertRuleDelete, 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": { @@ -149,7 +158,7 @@ func resourceArmMetricAlertRule() *schema.Resource { } } -func resourceArmMetricAlertRuleCreateOrUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceArmMetricAlertRuleCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).monitorAlertRulesClient ctx := meta.(*ArmClient).StopContext @@ -157,6 +166,21 @@ func resourceArmMetricAlertRuleCreateOrUpdate(d *schema.ResourceData, meta inter name := d.Get("name").(string) resourceGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Metric Alert Rule %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_metric_alertrule", *resp.ID) + } + } + location := azureRMNormalizeLocation(d.Get("location").(string)) tags := d.Get("tags").(map[string]interface{}) @@ -172,7 +196,9 @@ func resourceArmMetricAlertRuleCreateOrUpdate(d *schema.ResourceData, meta inter AlertRule: alertRule, } - _, err = client.CreateOrUpdate(ctx, resourceGroup, name, alertRuleResource) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err = client.CreateOrUpdate(waitCtx, resourceGroup, name, alertRuleResource) if err != nil { return err } @@ -194,11 +220,14 @@ func resourceArmMetricAlertRuleRead(d *schema.ResourceData, meta interface{}) er client := meta.(*ArmClient).monitorAlertRulesClient ctx := meta.(*ArmClient).StopContext - resourceGroup, name, err := resourceGroupAndAlertRuleNameFromId(d.Id()) + id, err := parseAzureResourceID(d.Id()) if err != nil { return err } + name := id.Path["alertrules"] + resourceGroup := id.ResourceGroup + resp, err := client.Get(ctx, resourceGroup, name) if err != nil { if utils.ResponseWasNotFound(resp.Response) { @@ -294,10 +323,12 @@ func resourceArmMetricAlertRuleDelete(d *schema.ResourceData, meta interface{}) client := meta.(*ArmClient).monitorAlertRulesClient ctx := meta.(*ArmClient).StopContext - resourceGroup, name, err := resourceGroupAndAlertRuleNameFromId(d.Id()) + id, err := parseAzureResourceID(d.Id()) if err != nil { return err } + name := id.Path["alertrules"] + resourceGroup := id.ResourceGroup resp, err := client.Delete(ctx, resourceGroup, name) if err != nil { @@ -410,17 +441,6 @@ func expandAzureRmMetricThresholdAlertRule(d *schema.ResourceData) (*insights.Al return &alertRule, nil } -func resourceGroupAndAlertRuleNameFromId(alertRuleId string) (string, string, error) { - id, err := parseAzureResourceID(alertRuleId) - if err != nil { - return "", "", err - } - name := id.Path["alertrules"] - resourceGroup := id.ResourceGroup - - return resourceGroup, name, nil -} - func validateMetricAlertRuleTags(v interface{}, f string) (ws []string, es []error) { // Normal validation required by any AzureRM resource. ws, es = validateAzureRMTags(v, f) diff --git a/azurerm/resource_arm_metric_alertrule_test.go b/azurerm/resource_arm_metric_alertrule_test.go index c4e0a2254b21..9ac7726cb351 100644 --- a/azurerm/resource_arm_metric_alertrule_test.go +++ b/azurerm/resource_arm_metric_alertrule_test.go @@ -117,6 +117,30 @@ func TestAccAzureRMMetricAlertRule_sqlDatabaseStorage(t *testing.T) { }) } +func TestAccAzureRMMetricAlertRule_requiresImport(t *testing.T) { + resourceName := "azurerm_metric_alertrule.test" + rInt := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMMetricAlertRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMMetricAlertRule_sqlDatabaseStorage(rInt, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMetricAlertRuleExists(resourceName), + ), + }, + { + Config: testAccAzureRMMetricAlertRule_requiresImport(rInt, location), + ExpectError: testRequiresImportError("azurerm_metric_alertrule"), + }, + }, + }) +} + func testCheckAzureRMMetricAlertRuleExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { // Ensure we have enough information in state to look up in API @@ -256,3 +280,39 @@ resource "azurerm_metric_alertrule" "test" { } `, basicSqlServerDatabase) } +func testAccAzureRMMetricAlertRule_requiresImport(rInt int, location string) string { + template := testAccAzureRMMetricAlertRule_sqlDatabaseStorage(rInt, location) + + return fmt.Sprintf(` +%s + +resource "azurerm_metric_alertrule" "import" { + name = "${azurerm_metric_alertrule.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + description = "An alert rule to watch the metric Storage" + enabled = true + resource_id = "${azurerm_sql_database.test.id}" + metric_name = "storage" + operator = "GreaterThan" + threshold = 1073741824 + aggregation = "Maximum" + period = "PT10M" + + email_action { + send_to_service_owners = false + custom_emails = [ + "support@azure.microsoft.com", + ] + } + + webhook_action { + service_uri = "https://requestb.in/18jamc41" + properties = { + severity = "incredible" + acceptance_test = "true" + } + } +} +`, template) +} diff --git a/azurerm/resource_arm_monitor_action_group.go b/azurerm/resource_arm_monitor_action_group.go index 4abbcd7ca4bc..d21c9bc3d040 100644 --- a/azurerm/resource_arm_monitor_action_group.go +++ b/azurerm/resource_arm_monitor_action_group.go @@ -1,24 +1,32 @@ package azurerm import ( + "context" "fmt" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/monitor/mgmt/2018-03-01/insights" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) func resourceArmMonitorActionGroup() *schema.Resource { return &schema.Resource{ - Create: resourceArmMonitorActionGroupCreateOrUpdate, + Create: resourceArmMonitorActionGroupCreateUpdate, Read: resourceArmMonitorActionGroupRead, - Update: resourceArmMonitorActionGroupCreateOrUpdate, + Update: resourceArmMonitorActionGroupCreateUpdate, Delete: resourceArmMonitorActionGroupDelete, 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": { @@ -109,13 +117,27 @@ func resourceArmMonitorActionGroup() *schema.Resource { } } -func resourceArmMonitorActionGroupCreateOrUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceArmMonitorActionGroupCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).actionGroupsClient ctx := meta.(*ArmClient).StopContext name := d.Get("name").(string) resGroup := d.Get("resource_group_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 Monitor Action Group %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_monitor_action_group", *resp.ID) + } + } + shortName := d.Get("short_name").(string) enabled := d.Get("enabled").(bool) @@ -138,7 +160,9 @@ func resourceArmMonitorActionGroupCreateOrUpdate(d *schema.ResourceData, meta in Tags: expandedTags, } - _, err := client.CreateOrUpdate(ctx, resGroup, name, parameters) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, resGroup, name, parameters) if err != nil { return fmt.Errorf("Error creating or updating action group %q (resource group %q): %+v", name, resGroup, err) } @@ -212,7 +236,9 @@ func resourceArmMonitorActionGroupDelete(d *schema.ResourceData, meta interface{ resGroup := id.ResourceGroup name := id.Path["actionGroups"] - 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 !response.WasNotFound(resp.Response) { return fmt.Errorf("Error deleting action group %q (resource group %q): %+v", name, resGroup, err) diff --git a/azurerm/resource_arm_monitor_action_group_test.go b/azurerm/resource_arm_monitor_action_group_test.go index 48e0a8a8fd13..306386bce8a9 100644 --- a/azurerm/resource_arm_monitor_action_group_test.go +++ b/azurerm/resource_arm_monitor_action_group_test.go @@ -39,6 +39,30 @@ func TestAccAzureRMMonitorActionGroup_basic(t *testing.T) { }) } +func TestAccAzureRMMonitorActionGroup_requiresImport(t *testing.T) { + resourceName := "azurerm_monitor_action_group.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMMonitorActionGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMMonitorActionGroup_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMonitorActionGroupExists(resourceName), + ), + }, + { + Config: testAccAzureRMMonitorActionGroup_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_monitor_action_group"), + }, + }, + }) +} + func TestAccAzureRMMonitorActionGroup_emailReceiver(t *testing.T) { resourceName := "azurerm_monitor_action_group.test" ri := acctest.RandInt() @@ -324,6 +348,19 @@ resource "azurerm_monitor_action_group" "test" { `, rInt, location, rInt) } +func testAccAzureRMMonitorActionGroup_requiresImport(rInt int, location string) string { + template := testAccAzureRMMonitorActionGroup_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_monitor_action_group" "import" { + name = "${azurerm_monitor_action_group.test.name}" + resource_group_name = "${azurerm_monitor_action_group.test.resource_group_name}" + short_name = "${azurerm_monitor_action_group.test.short_name}" +} +`, template) +} + func testAccAzureRMMonitorActionGroup_emailReceiver(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_mysql_configuration.go b/azurerm/resource_arm_mysql_configuration.go index c8d8f426a8c6..9422430a0bf2 100644 --- a/azurerm/resource_arm_mysql_configuration.go +++ b/azurerm/resource_arm_mysql_configuration.go @@ -1,8 +1,10 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/mysql/mgmt/2017-12-01/mysql" "github.com/hashicorp/terraform/helper/schema" @@ -17,6 +19,10 @@ func resourceArmMySQLConfiguration() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Schema: map[string]*schema.Schema{ "name": { @@ -48,6 +54,8 @@ func resourceArmMySQLConfigurationCreate(d *schema.ResourceData, meta interface{ log.Printf("[INFO] preparing arguments for AzureRM MySQL Configuration creation.") + // NOTE: we can't require import for this resource since these settings can have default values + // and having config containing the default values would be valid. name := d.Get("name").(string) resourceGroup := d.Get("resource_group_name").(string) serverName := d.Get("server_name").(string) @@ -65,7 +73,9 @@ func resourceArmMySQLConfigurationCreate(d *schema.ResourceData, meta interface{ return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -144,7 +154,9 @@ func resourceArmMySQLConfigurationDelete(d *schema.ResourceData, meta interface{ return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } diff --git a/azurerm/resource_arm_mysql_database.go b/azurerm/resource_arm_mysql_database.go index cf1b1f3bfcca..890a209c1c64 100644 --- a/azurerm/resource_arm_mysql_database.go +++ b/azurerm/resource_arm_mysql_database.go @@ -1,11 +1,14 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/mysql/mgmt/2017-12-01/mysql" "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 resourceArmMySqlDatabase() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Schema: map[string]*schema.Schema{ "name": { @@ -59,6 +66,18 @@ func resourceArmMySqlDatabaseCreate(d *schema.ResourceData, meta interface{}) er resourceGroup := d.Get("resource_group_name").(string) serverName := d.Get("server_name").(string) + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, serverName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of MySQL Database %q (Server %q / Resource Group %q): %+v", name, serverName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_mysql_database", *resp.ID) + } + charset := d.Get("charset").(string) collation := d.Get("collation").(string) @@ -74,7 +93,9 @@ func resourceArmMySqlDatabaseCreate(d *schema.ResourceData, meta interface{}) er return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -140,7 +161,9 @@ func resourceArmMySqlDatabaseDelete(d *schema.ResourceData, meta interface{}) er return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } diff --git a/azurerm/resource_arm_mysql_database_test.go b/azurerm/resource_arm_mysql_database_test.go index 3d7e729eb428..3548cde92083 100644 --- a/azurerm/resource_arm_mysql_database_test.go +++ b/azurerm/resource_arm_mysql_database_test.go @@ -30,6 +30,30 @@ func TestAccAzureRMMySQLDatabase_basic(t *testing.T) { }) } +func TestAccAzureRMMySQLDatabase_requiresImport(t *testing.T) { + resourceName := "azurerm_mysql_database.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMMySQLDatabaseDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMMySQLDatabase_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMySQLDatabaseExists(resourceName), + ), + }, + { + Config: testAccAzureRMMySQLDatabase_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_mysql_database"), + }, + }, + }) +} + func TestAccAzureRMMySQLDatabase_charsetUppercase(t *testing.T) { resourceName := "azurerm_mysql_database.test" ri := acctest.RandInt() @@ -169,6 +193,21 @@ resource "azurerm_mysql_database" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMMySQLDatabase_requiresImport(rInt int, location string) string { + template := testAccAzureRMMySQLDatabase_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_mysql_database" "import" { + name = "${azurerm_mysql_database.test.name}" + resource_group_name = "${azurerm_mysql_database.test.resource_group_name}" + server_name = "${azurerm_mysql_database.test.server_name}" + charset = "${azurerm_mysql_database.test.charset}" + collation = "${azurerm_mysql_database.test.collation}" +} +`, template) +} + func testAccAzureRMMySQLDatabase_charsetUppercase(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_mysql_firewall_rule.go b/azurerm/resource_arm_mysql_firewall_rule.go index d6842a0bfdf9..ef64ce46c716 100644 --- a/azurerm/resource_arm_mysql_firewall_rule.go +++ b/azurerm/resource_arm_mysql_firewall_rule.go @@ -1,11 +1,14 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/mysql/mgmt/2017-12-01/mysql" "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" ) @@ -18,6 +21,11 @@ func resourceArmMySqlFirewallRule() *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": { @@ -56,6 +64,21 @@ func resourceArmMySqlFirewallRuleCreateUpdate(d *schema.ResourceData, meta inter name := d.Get("name").(string) resourceGroup := d.Get("resource_group_name").(string) serverName := d.Get("server_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, serverName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Firewall Rule %q (Server %q / Resource Group %q): %+v", name, serverName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_mysql_firewall_rule", *resp.ID) + } + } + startIPAddress := d.Get("start_ip_address").(string) endIPAddress := d.Get("end_ip_address").(string) @@ -71,7 +94,9 @@ func resourceArmMySqlFirewallRuleCreateUpdate(d *schema.ResourceData, meta inter return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -136,7 +161,9 @@ func resourceArmMySqlFirewallRuleDelete(d *schema.ResourceData, meta interface{} return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } diff --git a/azurerm/resource_arm_mysql_firewall_rule_test.go b/azurerm/resource_arm_mysql_firewall_rule_test.go index b0e2a7efaaef..a12e77620e93 100644 --- a/azurerm/resource_arm_mysql_firewall_rule_test.go +++ b/azurerm/resource_arm_mysql_firewall_rule_test.go @@ -30,6 +30,30 @@ func TestAccAzureRMMySQLFirewallRule_basic(t *testing.T) { }) } +func TestAccAzureRMMySQLFirewallRule_requiresImport(t *testing.T) { + resourceName := "azurerm_mysql_firewall_rule.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMMySQLFirewallRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMMySQLFirewallRule_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMySQLFirewallRuleExists(resourceName), + ), + }, + { + Config: testAccAzureRMMySQLFirewallRule_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_mysql_firewall_rule"), + }, + }, + }) +} + func testCheckAzureRMMySQLFirewallRuleExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { // Ensure we have enough information in state to look up in API @@ -127,3 +151,18 @@ resource "azurerm_mysql_firewall_rule" "test" { } `, rInt, location, rInt, rInt) } + +func testAccAzureRMMySQLFirewallRule_requiresImport(rInt int, location string) string { + template := testAccAzureRMMySQLFirewallRule_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_mysql_firewall_rule" "import" { + name = "${azurerm_mysql_firewall_rule.test.name}" + resource_group_name = "${azurerm_mysql_firewall_rule.test.resource_group_name}" + server_name = "${azurerm_mysql_firewall_rule.test.server_name}" + start_ip_address = "${azurerm_mysql_firewall_rule.test.start_ip_address}" + end_ip_address = "${azurerm_mysql_firewall_rule.test.end_ip_address}" +} +`, template) +} diff --git a/azurerm/resource_arm_mysql_server.go b/azurerm/resource_arm_mysql_server.go index fcf0deaefb75..69945c3a0127 100644 --- a/azurerm/resource_arm_mysql_server.go +++ b/azurerm/resource_arm_mysql_server.go @@ -1,13 +1,16 @@ package azurerm import ( + "context" "fmt" "log" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/mysql/mgmt/2017-12-01/mysql" "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 resourceArmMySqlServer() *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": { @@ -192,9 +200,21 @@ func resourceArmMySqlServerCreate(d *schema.ResourceData, meta interface{}) erro log.Printf("[INFO] preparing arguments for AzureRM MySQL Server creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resourceGroup := d.Get("resource_group_name").(string) + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of MySQL Server %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_mysql_server", *resp.ID) + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) adminLogin := d.Get("administrator_login").(string) adminLoginPassword := d.Get("administrator_login_password").(string) sslEnforcement := d.Get("ssl_enforcement").(string) @@ -224,7 +244,9 @@ func resourceArmMySqlServerCreate(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("Error creating MySQL Server %q (Resource Group %q): %+v", name, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for creation of MySQL Server %q (Resource Group %q): %+v", name, resourceGroup, err) } @@ -275,7 +297,9 @@ func resourceArmMySqlServerUpdate(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("Error updating MySQL Server %q (Resource Group %q): %+v", name, resourceGroup, 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 fmt.Errorf("Error waiting for MySQL Server %q (Resource Group %q) to finish updating: %+v", name, resourceGroup, err) } @@ -358,7 +382,9 @@ func resourceArmMySqlServerDelete(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("Error deleting MySQL Server %q (Resource Group %q): %+v", name, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for deletion of MySQL Server %q (Resource Group %q): %+v", name, resourceGroup, err) } diff --git a/azurerm/resource_arm_mysql_server_test.go b/azurerm/resource_arm_mysql_server_test.go index 3fcaf8653ae1..a6a8a23ca2d5 100644 --- a/azurerm/resource_arm_mysql_server_test.go +++ b/azurerm/resource_arm_mysql_server_test.go @@ -30,6 +30,30 @@ func TestAccAzureRMMySQLServer_basicFiveSix(t *testing.T) { }) } +func TestAccAzureRMMySQLServer_requiresImport(t *testing.T) { + resourceName := "azurerm_mysql_server.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMMySQLServerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMMySQLServer_basicFiveSix(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMySQLServerExists(resourceName), + ), + }, + { + Config: testAccAzureRMMySQLServer_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_mysql_server"), + }, + }, + }) +} + func TestAccAzureRMMySQLServer_basicFiveSeven(t *testing.T) { resourceName := "azurerm_mysql_server.test" ri := acctest.RandInt() @@ -256,6 +280,37 @@ resource "azurerm_mysql_server" "test" { `, rInt, location, rInt) } +func testAccAzureRMMySQLServer_requiresImport(rInt int, location string) string { + template := testAccAzureRMMySQLServer_basicFiveSix(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_mysql_server" "import" { + name = "${azurerm_mysql_server.test.name}" + location = "${azurerm_mysql_server.test.location}" + resource_group_name = "${azurerm_mysql_server.test.resource_group_name}" + + sku { + name = "B_Gen4_2" + capacity = 2 + tier = "Basic" + family = "Gen4" + } + + storage_profile { + storage_mb = 51200 + backup_retention_days = 7 + geo_redundant_backup = "Disabled" + } + + administrator_login = "acctestun" + administrator_login_password = "H@Sh1CoR3!" + version = "5.6" + ssl_enforcement = "Enabled" +} +`, template) +} + func testAccAzureRMMySQLServer_basicFiveSeven(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_network_interface.go b/azurerm/resource_arm_network_interface.go index 69bb3aa5666b..0e4635086cc4 100644 --- a/azurerm/resource_arm_network_interface.go +++ b/azurerm/resource_arm_network_interface.go @@ -1,15 +1,18 @@ package azurerm import ( + "context" "fmt" "log" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/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/utils" ) @@ -20,10 +23,14 @@ func resourceArmNetworkInterface() *schema.Resource { Read: resourceArmNetworkInterfaceRead, Update: resourceArmNetworkInterfaceCreateUpdate, Delete: resourceArmNetworkInterfaceDelete, - 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": { @@ -230,8 +237,23 @@ func resourceArmNetworkInterfaceCreateUpdate(d *schema.ResourceData, meta interf log.Printf("[INFO] preparing arguments for AzureRM Network Interface creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_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 Network Interface %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_network_interface", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) enableIpForwarding := d.Get("enable_ip_forwarding").(bool) enableAcceleratedNetworking := d.Get("enable_accelerated_networking").(bool) tags := d.Get("tags").(map[string]interface{}) @@ -306,7 +328,9 @@ func resourceArmNetworkInterfaceCreateUpdate(d *schema.ResourceData, meta interf return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -475,7 +499,9 @@ func resourceArmNetworkInterfaceDelete(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error deleting Network Interface %q (Resource Group %q): %+v", name, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for the deletion of Network Interface %q (Resource Group %q): %+v", name, resGroup, err) } diff --git a/azurerm/resource_arm_network_interface_test.go b/azurerm/resource_arm_network_interface_test.go index 42aa33c97ae0..61296a994c8c 100644 --- a/azurerm/resource_arm_network_interface_test.go +++ b/azurerm/resource_arm_network_interface_test.go @@ -93,6 +93,28 @@ func TestAccAzureRMNetworkInterface_basic(t *testing.T) { }) } +func TestAccAzureRMNetworkInterface_requiresImport(t *testing.T) { + rInt := acctest.RandInt() + location := testLocation() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMNetworkInterfaceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMNetworkInterface_basic(rInt, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMNetworkInterfaceExists("azurerm_network_interface.test"), + ), + }, + { + Config: testAccAzureRMNetworkInterface_requiresImport(rInt, location), + ExpectError: testRequiresImportError("azurerm_network_interface"), + }, + }, + }) +} + func TestAccAzureRMNetworkInterface_disappears(t *testing.T) { resourceName := "azurerm_network_interface.test" rInt := acctest.RandInt() @@ -540,6 +562,25 @@ resource "azurerm_network_interface" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMNetworkInterface_requiresImport(rInt int, location string) string { + template := testAccAzureRMNetworkInterface_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_network_interface" "import" { + name = "${azurerm_network_interface.test.name}" + location = "${azurerm_network_interface.test.location}" + resource_group_name = "${azurerm_network_interface.test.resource_group_name}" + + ip_configuration { + name = "testconfiguration1" + subnet_id = "${azurerm_subnet.test.id}" + private_ip_address_allocation = "dynamic" + } +} +`, template) +} + func testAccAzureRMNetworkInterface_basicWithNetworkSecurityGroup(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_network_security_group.go b/azurerm/resource_arm_network_security_group.go index 8fba755eef6b..852876e4ef75 100644 --- a/azurerm/resource_arm_network_security_group.go +++ b/azurerm/resource_arm_network_security_group.go @@ -1,12 +1,15 @@ package azurerm import ( + "context" "fmt" + "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" multierror "github.com/hashicorp/go-multierror" "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" ) @@ -14,13 +17,18 @@ var networkSecurityGroupResourceName = "azurerm_network_security_group" func resourceArmNetworkSecurityGroup() *schema.Resource { return &schema.Resource{ - Create: resourceArmNetworkSecurityGroupCreate, + Create: resourceArmNetworkSecurityGroupCreateUpdate, Read: resourceArmNetworkSecurityGroupRead, - Update: resourceArmNetworkSecurityGroupCreate, + Update: resourceArmNetworkSecurityGroupCreateUpdate, Delete: resourceArmNetworkSecurityGroupDelete, 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": { @@ -157,13 +165,28 @@ func resourceArmNetworkSecurityGroup() *schema.Resource { } } -func resourceArmNetworkSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmNetworkSecurityGroupCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).secGroupClient ctx := meta.(*ArmClient).StopContext name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_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 Network Security Group %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_network_security_group", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) tags := d.Get("tags").(map[string]interface{}) sgRules, sgErr := expandAzureRmSecurityRules(d) @@ -188,7 +211,9 @@ func resourceArmNetworkSecurityGroupCreate(d *schema.ResourceData, meta interfac return fmt.Errorf("Error creating/updating NSG %q (Resource Group %q): %+v", name, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for the completion of NSG %q (Resource Group %q): %+v", name, resGroup, err) } @@ -260,7 +285,9 @@ func resourceArmNetworkSecurityGroupDelete(d *schema.ResourceData, meta interfac return fmt.Errorf("Error deleting Network Security Group %q (Resource Group %q): %+v", name, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error deleting Network Security Group %q (Resource Group %q): %+v", name, resGroup, err) } diff --git a/azurerm/resource_arm_network_security_group_test.go b/azurerm/resource_arm_network_security_group_test.go index 8cdc462c5b46..b5c28c40a9f5 100644 --- a/azurerm/resource_arm_network_security_group_test.go +++ b/azurerm/resource_arm_network_security_group_test.go @@ -29,6 +29,29 @@ func TestAccAzureRMNetworkSecurityGroup_basic(t *testing.T) { }) } +func TestAccAzureRMNetworkSecurityGroup_requiresImport(t *testing.T) { + resourceName := "azurerm_network_security_group.test" + rInt := acctest.RandInt() + location := testLocation() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMNetworkSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMNetworkSecurityGroup_basic(rInt, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMNetworkSecurityGroupExists(resourceName), + ), + }, + { + Config: testAccAzureRMNetworkSecurityGroup_requiresImport(rInt, location), + ExpectError: testRequiresImportError("azurerm_network_security_group"), + }, + }, + }) +} + func TestAccAzureRMNetworkSecurityGroup_singleRule(t *testing.T) { resourceName := "azurerm_network_security_group.test" rInt := acctest.RandInt() @@ -285,6 +308,19 @@ resource "azurerm_network_security_group" "test" { `, rInt, location) } +func testAccAzureRMNetworkSecurityGroup_requiresImport(rInt int, location string) string { + template := testAccAzureRMNetworkSecurityGroup_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_network_security_group" "import" { + name = "${azurerm_network_security_group.test.name}" + location = "${azurerm_network_security_group.test.location}" + resource_group_name = "${azurerm_network_security_group.test.resource_group_name}" +} +`, template) +} + func testAccAzureRMNetworkSecurityGroup_singleRule(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_network_security_rule.go b/azurerm/resource_arm_network_security_rule.go index 66156c9bec1d..3e6c7c6423ec 100644 --- a/azurerm/resource_arm_network_security_rule.go +++ b/azurerm/resource_arm_network_security_rule.go @@ -1,23 +1,31 @@ package azurerm import ( + "context" "fmt" + "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" "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" ) func resourceArmNetworkSecurityRule() *schema.Resource { return &schema.Resource{ - Create: resourceArmNetworkSecurityRuleCreate, + Create: resourceArmNetworkSecurityRuleCreateUpdate, Read: resourceArmNetworkSecurityRuleRead, - Update: resourceArmNetworkSecurityRuleCreate, + Update: resourceArmNetworkSecurityRuleCreateUpdate, Delete: resourceArmNetworkSecurityRuleDelete, 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": { @@ -152,7 +160,7 @@ func resourceArmNetworkSecurityRule() *schema.Resource { } } -func resourceArmNetworkSecurityRuleCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmNetworkSecurityRuleCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).secRuleClient ctx := meta.(*ArmClient).StopContext @@ -160,6 +168,20 @@ func resourceArmNetworkSecurityRuleCreate(d *schema.ResourceData, meta interface nsgName := d.Get("network_security_group_name").(string) resGroup := d.Get("resource_group_name").(string) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, nsgName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Network Security Rule %q (Security Group %q Resource Group %q): %+v", name, nsgName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_network_security_rule", *resp.ID) + } + } + sourcePortRange := d.Get("source_port_range").(string) destinationPortRange := d.Get("destination_port_range").(string) sourceAddressPrefix := d.Get("source_address_prefix").(string) @@ -258,7 +280,9 @@ func resourceArmNetworkSecurityRuleCreate(d *schema.ResourceData, meta interface return fmt.Errorf("Error Creating/Updating Network Security Rule %q (NSG %q / Resource Group %q): %+v", name, nsgName, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for completion of Network Security Rule %q (NSG %q / Resource Group %q): %+v", name, nsgName, resGroup, err) } @@ -340,7 +364,9 @@ func resourceArmNetworkSecurityRuleDelete(d *schema.ResourceData, meta interface return fmt.Errorf("Error Deleting Network Security Rule %q (NSG %q / Resource Group %q): %+v", sgRuleName, nsgName, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for the deletion of Network Security Rule %q (NSG %q / Resource Group %q): %+v", sgRuleName, nsgName, resGroup, err) } diff --git a/azurerm/resource_arm_network_security_rule_test.go b/azurerm/resource_arm_network_security_rule_test.go index 4389d66c98c5..e60ba34fdb93 100644 --- a/azurerm/resource_arm_network_security_rule_test.go +++ b/azurerm/resource_arm_network_security_rule_test.go @@ -29,6 +29,28 @@ func TestAccAzureRMNetworkSecurityRule_basic(t *testing.T) { }) } +func TestAccAzureRMNetworkSecurityRule_requiresImport(t *testing.T) { + rInt := acctest.RandInt() + location := testLocation() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMNetworkSecurityRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMNetworkSecurityRule_basic(rInt, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMNetworkSecurityRuleExists("azurerm_network_security_rule.test"), + ), + }, + { + Config: testAccAzureRMNetworkSecurityRule_requiresImport(rInt, location), + ExpectError: testRequiresImportError("azurerm_network_security_rule"), + }, + }, + }) +} + func TestAccAzureRMNetworkSecurityRule_disappears(t *testing.T) { resourceGroup := "azurerm_network_security_rule.test" rInt := acctest.RandInt() @@ -224,6 +246,27 @@ resource "azurerm_network_security_rule" "test" { `, rInt, location) } +func testAccAzureRMNetworkSecurityRule_requiresImport(rInt int, location string) string { + template := testAccAzureRMNetworkSecurityRule_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_network_security_rule" "import" { + name = "${azurerm_network_security_rule.test.name}" + priority = "${azurerm_network_security_rule.test.priority}" + direction = "${azurerm_network_security_rule.test.direction}" + access = "${azurerm_network_security_rule.test.access}" + protocol = "${azurerm_network_security_rule.test.protocol}" + source_port_range = "${azurerm_network_security_rule.test.source_port_range}" + destination_port_range = "${azurerm_network_security_rule.test.destination_port_range}" + source_address_prefix = "${azurerm_network_security_rule.test.source_address_prefix}" + destination_address_prefix = "${azurerm_network_security_rule.test.destination_address_prefix}" + resource_group_name = "${azurerm_network_security_rule.test.resource_group_name}" + network_security_group_name = "${azurerm_network_security_rule.test.network_security_group_name}" +} +`, template) +} + func testAccAzureRMNetworkSecurityRule_updateBasic(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test1" { diff --git a/azurerm/resource_arm_network_watcher.go b/azurerm/resource_arm_network_watcher.go index 5452a2703c5d..663bb850cb11 100644 --- a/azurerm/resource_arm_network_watcher.go +++ b/azurerm/resource_arm_network_watcher.go @@ -1,11 +1,14 @@ package azurerm import ( + "context" "fmt" + "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -18,6 +21,11 @@ func resourceArmNetworkWatcher() *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": { @@ -41,6 +49,21 @@ func resourceArmNetworkWatcherCreateUpdate(d *schema.ResourceData, meta interfac name := d.Get("name").(string) resourceGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Network Watcher %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_network_watcher", *resp.ID) + } + } + location := azureRMNormalizeLocation(d.Get("location").(string)) tags := d.Get("tags").(map[string]interface{}) @@ -48,7 +71,10 @@ func resourceArmNetworkWatcherCreateUpdate(d *schema.ResourceData, meta interfac Location: utils.String(location), Tags: expandTags(tags), } - _, err := client.CreateOrUpdate(ctx, resourceGroup, name, watcher) + + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, resourceGroup, name, watcher) if err != nil { return err } @@ -115,7 +141,9 @@ func resourceArmNetworkWatcherDelete(d *schema.ResourceData, meta interface{}) e } } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for the deletion of Network Watcher %q (Resource Group %q): %+v", name, resourceGroup, err) } diff --git a/azurerm/resource_arm_network_watcher_test.go b/azurerm/resource_arm_network_watcher_test.go index be0169c909a8..879f759b298b 100644 --- a/azurerm/resource_arm_network_watcher_test.go +++ b/azurerm/resource_arm_network_watcher_test.go @@ -23,10 +23,12 @@ func TestAccAzureRMNetworkWatcher(t *testing.T) { "disappears": testAccAzureRMNetworkWatcher_disappears, "importBasic": testAccAzureRMNetworkWatcher_importBasic, "importComplete": testAccAzureRMNetworkWatcher_importComplete, + "requiresImport": testAccAzureRMNetworkWatcher_requiresImport, }, "PacketCapture": { "import": testAccAzureRMPacketCapture_importBasic, "localDisk": testAccAzureRMPacketCapture_localDisk, + "requiresImport": testAccAzureRMPacketCapture_requiresImport, "storageAccount": testAccAzureRMPacketCapture_storageAccount, "storageAccountAndLocalDisk": testAccAzureRMPacketCapture_storageAccountAndLocalDisk, "withFilters": testAccAzureRMPacketCapture_withFilters, @@ -64,6 +66,29 @@ func testAccAzureRMNetworkWatcher_basic(t *testing.T) { }) } +func testAccAzureRMNetworkWatcher_requiresImport(t *testing.T) { + resourceGroup := "azurerm_network_watcher.test" + rInt := acctest.RandInt() + location := testLocation() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMNetworkWatcherDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMNetworkWatcher_basicConfig(rInt, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMNetworkWatcherExists(resourceGroup), + ), + }, + { + Config: testAccAzureRMNetworkWatcher_requiresImportConfig(rInt, location), + ExpectError: testRequiresImportError("azurerm_network_watcher"), + }, + }, + }) +} + func testAccAzureRMNetworkWatcher_complete(t *testing.T) { resourceGroup := "azurerm_network_watcher.test" rInt := acctest.RandInt() @@ -227,6 +252,19 @@ resource "azurerm_network_watcher" "test" { `, rInt, location, rInt) } +func testAccAzureRMNetworkWatcher_requiresImportConfig(rInt int, location string) string { + template := testAccAzureRMNetworkWatcher_basicConfig(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_network_watcher" "import" { + name = "${azurerm_network_watcher.test.name}" + location = "${azurerm_network_watcher.test.location}" + resource_group_name = "${azurerm_network_watcher.test.resource_group_name}" +} +`, template) +} + func testAccAzureRMNetworkWatcher_completeConfig(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_notification_hub.go b/azurerm/resource_arm_notification_hub.go index e0bb33203e95..586f3885039a 100644 --- a/azurerm/resource_arm_notification_hub.go +++ b/azurerm/resource_arm_notification_hub.go @@ -1,12 +1,15 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/notificationhubs/mgmt/2017-04-01/notificationhubs" "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" ) @@ -24,6 +27,12 @@ func resourceArmNotificationHub() *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), + }, + CustomizeDiff: func(diff *schema.ResourceDiff, v interface{}) error { // NOTE: the ForceNew is to workaround a bug in the Azure SDK where nil-values aren't sent to the API. // Bug: https://github.com/Azure/azure-sdk-for-go/issues/2246 @@ -130,8 +139,22 @@ func resourceArmNotificationHubCreateUpdate(d *schema.ResourceData, meta interfa name := d.Get("name").(string) namespaceName := d.Get("namespace_name").(string) resourceGroup := d.Get("resource_group_name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, namespaceName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Notification Hub %q (Namespace %q / Resource Group %q): %+v", name, namespaceName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_notification_hub", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) apnsRaw := d.Get("apns_credential").([]interface{}) apnsCredential, err := expandNotificationHubsAPNSCredentials(apnsRaw) if err != nil { @@ -152,7 +175,9 @@ func resourceArmNotificationHubCreateUpdate(d *schema.ResourceData, meta interfa }, } - _, err = client.CreateOrUpdate(ctx, resourceGroup, namespaceName, name, parameters) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err = client.CreateOrUpdate(waitCtx, resourceGroup, namespaceName, name, parameters) if err != nil { return fmt.Errorf("Error creating Notification Hub %q (Namespace %q / Resource Group %q): %+v", name, namespaceName, resourceGroup, err) } @@ -232,7 +257,9 @@ func resourceArmNotificationHubDelete(d *schema.ResourceData, meta interface{}) namespaceName := id.Path["namespaces"] name := id.Path["notificationHubs"] - resp, err := client.Delete(ctx, resourceGroup, namespaceName, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resourceGroup, namespaceName, name) if err != nil { if !utils.ResponseWasNotFound(resp) { return fmt.Errorf("Error deleting Notification Hub %q (Namespace %q / Resource Group %q): %+v", name, namespaceName, resourceGroup, err) diff --git a/azurerm/resource_arm_notification_hub_authorization_rule.go b/azurerm/resource_arm_notification_hub_authorization_rule.go index 6ae0bcfee923..337404c9305f 100644 --- a/azurerm/resource_arm_notification_hub_authorization_rule.go +++ b/azurerm/resource_arm_notification_hub_authorization_rule.go @@ -1,11 +1,14 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/notificationhubs/mgmt/2017-04-01/notificationhubs" "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" ) @@ -18,6 +21,11 @@ func resourceArmNotificationHubAuthorizationRule() *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), + }, // TODO: customizeDiff for send+listen when manage selected Schema: map[string]*schema.Schema{ @@ -81,6 +89,20 @@ func resourceArmNotificationHubAuthorizationRuleCreateUpdate(d *schema.ResourceD namespaceName := d.Get("namespace_name").(string) resourceGroup := d.Get("resource_group_name").(string) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.GetAuthorizationRule(ctx, resourceGroup, namespaceName, notificationHubName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Authorization Rule %q (Notification Hub %q / Namespace %q / Resource Group %q): %+v", name, notificationHubName, namespaceName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_notification_hub_authorization_rule", *resp.ID) + } + } + manage := d.Get("manage").(bool) send := d.Get("send").(bool) listen := d.Get("listen").(bool) @@ -91,7 +113,9 @@ func resourceArmNotificationHubAuthorizationRuleCreateUpdate(d *schema.ResourceD }, } - _, err := client.CreateOrUpdateAuthorizationRule(ctx, resourceGroup, namespaceName, notificationHubName, name, parameters) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdateAuthorizationRule(waitCtx, resourceGroup, namespaceName, notificationHubName, name, parameters) if err != nil { return fmt.Errorf("Error creating Authorization Rule %q (Notification Hub %q / Namespace %q / Resource Group %q): %+v", name, notificationHubName, namespaceName, resourceGroup, err) } @@ -169,7 +193,9 @@ func resourceArmNotificationHubAuthorizationRuleDelete(d *schema.ResourceData, m notificationHubName := id.Path["notificationHubs"] name := id.Path["AuthorizationRules"] - resp, err := client.DeleteAuthorizationRule(ctx, resourceGroup, namespaceName, notificationHubName, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.DeleteAuthorizationRule(waitCtx, resourceGroup, namespaceName, notificationHubName, name) if err != nil { if !utils.ResponseWasNotFound(resp) { return fmt.Errorf("Error deleting Authorization Rule %q (Notification Hub %q / Namespace %q / Resource Group %q): %+v", name, notificationHubName, namespaceName, resourceGroup, err) diff --git a/azurerm/resource_arm_notification_hub_authorization_rule_test.go b/azurerm/resource_arm_notification_hub_authorization_rule_test.go index 7d2cf480ee70..95bf83ba44c4 100644 --- a/azurerm/resource_arm_notification_hub_authorization_rule_test.go +++ b/azurerm/resource_arm_notification_hub_authorization_rule_test.go @@ -36,6 +36,31 @@ func TestAccAzureRMNotificationHubAuthorizationRule_listen(t *testing.T) { }) } +func TestAccAzureRMNotificationHubAuthorizationRule_requiresImport(t *testing.T) { + resourceName := "azurerm_notification_hub_authorization_rule.test" + + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMNotificationHubAuthorizationRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAzureRMNotificationHubAuthorizationRule_listen(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMNotificationHubAuthorizationRuleExists(resourceName), + ), + }, + { + Config: testAzureRMNotificationHubAuthorizationRule_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_notification_hub_authorization_rule"), + }, + }, + }) +} + func TestAccAzureRMNotificationHubAuthorizationRule_manage(t *testing.T) { resourceName := "azurerm_notification_hub_authorization_rule.test" @@ -195,6 +220,21 @@ resource "azurerm_notification_hub_authorization_rule" "test" { `, template, ri) } +func testAzureRMNotificationHubAuthorizationRule_requiresImport(rInt int, location string) string { + template := testAzureRMNotificationHubAuthorizationRule_listen(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_notification_hub_authorization_rule" "import" { + name = "${azurerm_notification_hub_authorization_rule.test.name}" + notification_hub_name = "${azurerm_notification_hub_authorization_rule.test.notification_hub_name}" + namespace_name = "${azurerm_notification_hub_authorization_rule.test.namespace_name}" + resource_group_name = "${azurerm_notification_hub_authorization_rule.test.resource_group_name}" + listen = true +} +`, template) +} + func testAzureRMNotificationHubAuthorizationRule_send(ri int, location string) string { template := testAzureRMNotificationHubAuthorizationRule_template(ri, location) return fmt.Sprintf(` diff --git a/azurerm/resource_arm_notification_hub_namespace.go b/azurerm/resource_arm_notification_hub_namespace.go index a672c84ec51a..88474a47dd3e 100644 --- a/azurerm/resource_arm_notification_hub_namespace.go +++ b/azurerm/resource_arm_notification_hub_namespace.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -24,6 +25,11 @@ func resourceArmNotificationHubNamespace() *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": { @@ -90,8 +96,22 @@ func resourceArmNotificationHubNamespaceCreateUpdate(d *schema.ResourceData, met name := d.Get("name").(string) resourceGroup := d.Get("resource_group_name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Notification Hub Namespace %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_notification_hub_namespace", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) sku := expandNotificationHubNamespacesSku(d.Get("sku").([]interface{})) namespaceType := d.Get("namespace_type").(string) @@ -106,7 +126,10 @@ func resourceArmNotificationHubNamespaceCreateUpdate(d *schema.ResourceData, met Enabled: utils.Bool(enabled), }, } - _, err := client.CreateOrUpdate(ctx, resourceGroup, name, parameters) + + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, resourceGroup, name, parameters) if err != nil { return fmt.Errorf("Error creating/updating Notification Hub Namesapce %q (Resource Group %q): %+v", name, resourceGroup, err) } @@ -187,11 +210,13 @@ func resourceArmNotificationHubNamespaceDelete(d *schema.ResourceData, meta inte // the future returned from the Delete method is broken 50% of the time - let's poll ourselves for now // Related Bug: https://github.com/Azure/azure-sdk-for-go/issues/2254 log.Printf("[DEBUG] Waiting for Notification Hub Namespace %q (Resource Group %q) to be deleted", name, resourceGroup) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() stateConf := &resource.StateChangeConf{ Pending: []string{"200", "202"}, Target: []string{"404"}, - Refresh: notificationHubNamespaceStateRefreshFunc(ctx, client, resourceGroup, name), - Timeout: 10 * time.Minute, + Refresh: notificationHubNamespaceStateRefreshFunc(waitCtx, client, resourceGroup, name), + Timeout: d.Timeout(schema.TimeoutDelete), } if _, err := stateConf.WaitForState(); err != nil { return fmt.Errorf("Error waiting for Notification Hub %q (Resource Group %q) to be deleted: %s", name, resourceGroup, err) diff --git a/azurerm/resource_arm_notification_hub_namespace_test.go b/azurerm/resource_arm_notification_hub_namespace_test.go index 828b9a741fee..ddfd411c8131 100644 --- a/azurerm/resource_arm_notification_hub_namespace_test.go +++ b/azurerm/resource_arm_notification_hub_namespace_test.go @@ -31,6 +31,31 @@ func TestAccAzureRMNotificationHubNamespace_free(t *testing.T) { }) } +func TestAccAzureRMNotificationHubNamespace_requiresImport(t *testing.T) { + resourceName := "azurerm_notification_hub_namespace.test" + + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMNotificationHubNamespaceDestroy, + Steps: []resource.TestStep{ + { + Config: testAzureRMNotificationHubNamespace_free(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMNotificationHubNamespaceExists(resourceName), + ), + }, + { + Config: testAzureRMNotificationHubNamespace_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_notification_hub_namespace"), + }, + }, + }) +} + func testCheckAzureRMNotificationHubNamespaceExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] @@ -102,22 +127,20 @@ resource "azurerm_notification_hub_namespace" "test" { `, ri, location, ri) } -func testAzureRMNotificationHubNamespace_basic(ri int, location string) string { +func testAzureRMNotificationHubNamespace_requiresImport(rInt int, location string) string { + template := testAzureRMNotificationHubNamespace_free(rInt, location) return fmt.Sprintf(` -resource "azurerm_resource_group" "test" { - name = "acctestrg-%d" - location = "%s" -} +%s -resource "azurerm_notification_hub_namespace" "test" { - name = "acctestnhn-%d" - resource_group_name = "${azurerm_resource_group.test.name}" - location = "${azurerm_resource_group.test.location}" - namespace_type = "NotificationHub" +resource "azurerm_notification_hub_namespace" "import" { + name = "${azurerm_notification_hub_namespace.test.name}" + resource_group_name = "${azurerm_notification_hub_namespace.test.resource_group_name}" + location = "${azurerm_notification_hub_namespace.test.location}" + namespace_type = "${azurerm_notification_hub_namespace.test.namespace_type}" sku { - name = "Basic" + name = "Free" } } -`, ri, location, ri) +`, template) } diff --git a/azurerm/resource_arm_notification_hub_test.go b/azurerm/resource_arm_notification_hub_test.go index ef57f9c34558..6c2451f2cc9c 100644 --- a/azurerm/resource_arm_notification_hub_test.go +++ b/azurerm/resource_arm_notification_hub_test.go @@ -33,6 +33,31 @@ func TestAccAzureRMNotificationHub_basic(t *testing.T) { }) } +func TestAccAzureRMNotificationHub_requiresImport(t *testing.T) { + resourceName := "azurerm_notification_hub.test" + + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMNotificationHubDestroy, + Steps: []resource.TestStep{ + { + Config: testAzureRMNotificationHub_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMNotificationHubExists(resourceName), + ), + }, + { + Config: testAzureRMNotificationHub_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_notification_hub"), + }, + }, + }) +} + func testCheckAzureRMNotificationHubExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] @@ -112,3 +137,17 @@ resource "azurerm_notification_hub" "test" { } `, ri, location, ri, ri) } + +func testAzureRMNotificationHub_requiresImport(rInt int, location string) string { + template := testAzureRMNotificationHub_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_notification_hub" "import" { + name = "${azurerm_notification_hub.test.name}" + namespace_name = "${azurerm_notification_hub.test.namespace_name}" + resource_group_name = "${azurerm_notification_hub.test.resource_group_name}" + location = "${azurerm_notification_hub.test.location}" +} +`, template) +} diff --git a/azurerm/resource_arm_packet_capture.go b/azurerm/resource_arm_packet_capture.go index ee7747aeb15a..8ac6d7f46986 100644 --- a/azurerm/resource_arm_packet_capture.go +++ b/azurerm/resource_arm_packet_capture.go @@ -1,7 +1,9 @@ package azurerm import ( + "context" "fmt" + "time" "log" @@ -9,6 +11,7 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -20,6 +23,10 @@ func resourceArmPacketCapture() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Schema: map[string]*schema.Schema{ "name": { @@ -138,6 +145,18 @@ func resourceArmPacketCaptureCreate(d *schema.ResourceData, meta interface{}) er watcherName := d.Get("network_watcher_name").(string) resourceGroup := d.Get("resource_group_name").(string) + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, watcherName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Packet Capture %q (Watcher %q / Resource Group %q): %+v", name, watcherName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_packet_capture", *resp.ID) + } + targetResourceId := d.Get("target_resource_id").(string) bytesToCapturePerPacket := d.Get("maximum_bytes_per_packet").(int) totalBytesPerSession := d.Get("maximum_bytes_per_session").(int) @@ -169,12 +188,14 @@ func resourceArmPacketCaptureCreate(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("Error creating Packet Capture %q (Watcher %q / Resource Group %q): %+v", name, watcherName, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for creation of Packet Capture %q (Watcher %q / Resource Group %q): %+v", name, watcherName, resourceGroup, err) } - resp, err := client.Get(ctx, resourceGroup, watcherName, name) + resp, err = client.Get(ctx, resourceGroup, watcherName, name) if err != nil { return fmt.Errorf("Error retrieving Packet Capture %q (Watcher %q / Resource Group %q): %+v", name, watcherName, resourceGroup, err) } @@ -254,7 +275,9 @@ func resourceArmPacketCaptureDelete(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("Error deleting Packet Capture %q (Watcher %q / Resource Group %q): %+v", name, watcherName, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if response.WasNotFound(future.Response()) { return nil diff --git a/azurerm/resource_arm_packet_capture_test.go b/azurerm/resource_arm_packet_capture_test.go index ecfa2b7ade9a..0614e0555d08 100644 --- a/azurerm/resource_arm_packet_capture_test.go +++ b/azurerm/resource_arm_packet_capture_test.go @@ -31,6 +31,31 @@ func testAccAzureRMPacketCapture_localDisk(t *testing.T) { }) } +func testAccAzureRMPacketCapture_requiresImport(t *testing.T) { + resourceName := "azurerm_packet_capture.test" + + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMPacketCaptureDestroy, + Steps: []resource.TestStep{ + { + Config: testAzureRMPacketCapture_localDiskConfig(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMPacketCaptureExists(resourceName), + ), + }, + { + Config: testAzureRMPacketCapture_requiresImportConfig(ri, location), + ExpectError: testRequiresImportError("azurerm_packet_capture"), + }, + }, + }) +} + func testAccAzureRMPacketCapture_storageAccount(t *testing.T) { resourceName := "azurerm_packet_capture.test" @@ -254,6 +279,26 @@ resource "azurerm_packet_capture" "test" { `, config, rInt) } +func testAzureRMPacketCapture_requiresImportConfig(rInt int, location string) string { + template := testAzureRMPacketCapture_localDiskConfig(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_packet_capture" "import" { + name = "${azurerm_packet_capture.test.name}" + network_watcher_name = "${azurerm_packet_capture.test.network_watcher_name}" + resource_group_name = "${azurerm_packet_capture.test.resource_group_name}" + target_resource_id = "${azurerm_packet_capture.test.target_resource_id}" + + storage_location { + file_path = "/var/captures/packet.cap" + } + + depends_on = ["azurerm_virtual_machine_extension.test"] +} +`, template) +} + func testAzureRMPacketCapture_localDiskConfigWithFilters(rInt int, location string) string { config := testAzureRMPacketCapture_base(rInt, location) return fmt.Sprintf(` diff --git a/azurerm/resource_arm_policy_assignment.go b/azurerm/resource_arm_policy_assignment.go index 4a18126a0458..c42915f08dd0 100644 --- a/azurerm/resource_arm_policy_assignment.go +++ b/azurerm/resource_arm_policy_assignment.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/structure" "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" ) @@ -25,6 +26,10 @@ func resourceArmPolicyAssignment() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 5), + Delete: schema.DefaultTimeout(time.Minute * 5), + }, Schema: map[string]*schema.Schema{ "name": { @@ -75,6 +80,18 @@ func resourceArmPolicyAssignmentCreate(d *schema.ResourceData, meta interface{}) name := d.Get("name").(string) scope := d.Get("scope").(string) + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, scope, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Policy Assignment %q (Scope %q): %+v", name, scope, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_policy_assignment", *resp.ID) + } + policyDefinitionId := d.Get("policy_definition_id").(string) displayName := d.Get("display_name").(string) @@ -99,18 +116,21 @@ func resourceArmPolicyAssignmentCreate(d *schema.ResourceData, meta interface{}) assignment.AssignmentProperties.Parameters = &expandedParams } - _, err := client.Create(ctx, scope, name, assignment) + _, err = client.Create(ctx, scope, name, assignment) if err != nil { return err } // Policy Assignments are eventually consistent; wait for them to stabilize log.Printf("[DEBUG] Waiting for Policy Assignment %q to become available", name) + timeout := d.Timeout(schema.TimeoutCreate) + waitCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() stateConf := &resource.StateChangeConf{ Pending: []string{"404"}, Target: []string{"200"}, - Refresh: policyAssignmentRefreshFunc(ctx, client, scope, name), - Timeout: 5 * time.Minute, + Refresh: policyAssignmentRefreshFunc(waitCtx, client, scope, name), + Timeout: timeout, MinTimeout: 10 * time.Second, ContinuousTargetOccurence: 10, } @@ -118,7 +138,7 @@ func resourceArmPolicyAssignmentCreate(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error waiting for Policy Assignment %q to become available: %s", name, err) } - resp, err := client.Get(ctx, scope, name) + resp, err = client.Get(ctx, scope, name) if err != nil { return err } @@ -170,10 +190,11 @@ func resourceArmPolicyAssignmentRead(d *schema.ResourceData, meta interface{}) e func resourceArmPolicyAssignmentDelete(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).policyAssignmentsClient ctx := meta.(*ArmClient).StopContext - id := d.Id() - resp, err := client.DeleteByID(ctx, id) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.DeleteByID(waitCtx, id) if err != nil { if utils.ResponseWasNotFound(resp.Response) { return nil diff --git a/azurerm/resource_arm_policy_assignment_test.go b/azurerm/resource_arm_policy_assignment_test.go index d57c280b34f5..68725eba9eae 100644 --- a/azurerm/resource_arm_policy_assignment_test.go +++ b/azurerm/resource_arm_policy_assignment_test.go @@ -31,6 +31,31 @@ func TestAccAzureRMPolicyAssignment_basic(t *testing.T) { }) } +func TestAccAzureRMPolicyAssignment_requiresImport(t *testing.T) { + resourceName := "azurerm_policy_assignment.test" + + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMPolicyAssignmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAzureRMPolicyAssignment_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMPolicyAssignmentExists(resourceName), + ), + }, + { + Config: testAzureRMPolicyAssignment_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_policy_assignment"), + }, + }, + }) +} + func TestAccAzureRMPolicyAssignment_complete(t *testing.T) { resourceName := "azurerm_policy_assignment.test" @@ -135,6 +160,19 @@ resource "azurerm_policy_assignment" "test" { `, ri, ri, location, ri, location, ri) } +func testAzureRMPolicyAssignment_requiresImport(rInt int, location string) string { + template := testAzureRMPolicyAssignment_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_policy_assignment" "import" { + name = "${azurerm_policy_assignment.test.name}" + scope = "${azurerm_policy_assignment.test.scope}" + policy_definition_id = "${azurerm_policy_assignment.test.policy_definition_id}" +} +`, template) +} + func testAzureRMPolicyAssignment_complete(ri int, location string) string { return fmt.Sprintf(` resource "azurerm_policy_definition" "test" { diff --git a/azurerm/resource_arm_policy_definition.go b/azurerm/resource_arm_policy_definition.go index 9bdfcb1bed8f..4352c6c8b2cb 100644 --- a/azurerm/resource_arm_policy_definition.go +++ b/azurerm/resource_arm_policy_definition.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/structure" "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" ) @@ -27,6 +28,11 @@ func resourceArmPolicyDefinition() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 10), + Update: schema.DefaultTimeout(time.Minute * 10), + Delete: schema.DefaultTimeout(time.Minute * 10), + }, Schema: map[string]*schema.Schema{ "name": { @@ -95,6 +101,21 @@ func resourceArmPolicyDefinitionCreateUpdate(d *schema.ResourceData, meta interf ctx := meta.(*ArmClient).StopContext name := d.Get("name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Policy Definition %q: %+v", name, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_policy_definition", *resp.ID) + } + } + policyType := d.Get("policy_type").(string) mode := d.Get("mode").(string) displayName := d.Get("display_name").(string) @@ -143,11 +164,14 @@ func resourceArmPolicyDefinitionCreateUpdate(d *schema.ResourceData, meta interf // Policy Definitions are eventually consistent; wait for them to stabilize log.Printf("[DEBUG] Waiting for Policy Definition %q to become available", name) + timeout := d.Timeout(tf.TimeoutForCreateUpdate(d)) + waitCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() stateConf := &resource.StateChangeConf{ Pending: []string{"404"}, Target: []string{"200"}, - Refresh: policyDefinitionRefreshFunc(ctx, client, name), - Timeout: 5 * time.Minute, + Refresh: policyDefinitionRefreshFunc(waitCtx, client, name), + Timeout: timeout, MinTimeout: 10 * time.Second, ContinuousTargetOccurence: 10, } @@ -236,8 +260,9 @@ func resourceArmPolicyDefinitionDelete(d *schema.ResourceData, meta interface{}) return err } - resp, err := client.Delete(ctx, name) - + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, name) if err != nil { if utils.ResponseWasNotFound(resp) { return nil diff --git a/azurerm/resource_arm_policy_definition_test.go b/azurerm/resource_arm_policy_definition_test.go index 3e4f970b3977..2ccb55ce7473 100644 --- a/azurerm/resource_arm_policy_definition_test.go +++ b/azurerm/resource_arm_policy_definition_test.go @@ -30,6 +30,30 @@ func TestAccAzureRMPolicyDefinition_basic(t *testing.T) { }) } +func TestAccAzureRMPolicyDefinition_requiresImport(t *testing.T) { + resourceName := "azurerm_policy_definition.test" + + ri := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMPolicyDefinitionDestroy, + Steps: []resource.TestStep{ + { + Config: testAzureRMPolicyDefinition_basic(ri), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMPolicyDefinitionExists(resourceName), + ), + }, + { + Config: testAzureRMPolicyDefinition_requiresImport(ri), + ExpectError: testRequiresImportError("azurerm_policy_definition"), + }, + }, + }) +} + func testCheckAzureRMPolicyDefinitionExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] @@ -116,3 +140,19 @@ PARAMETERS } `, ri, ri) } + +func testAzureRMPolicyDefinition_requiresImport(rInt int) string { + template := testAzureRMPolicyDefinition_basic(rInt) + return fmt.Sprintf(` +%s + +resource "azurerm_policy_definition" "import" { + name = "${azurerm_policy_definition.test.name}" + policy_type = "${azurerm_policy_definition.test.policy_type}" + mode = "${azurerm_policy_definition.test.mode}" + display_name = "${azurerm_policy_definition.test.display_name}" + policy_rule = "${azurerm_policy_definition.test.policy_rule}" + parameters = "${azurerm_policy_definition.test.parameters}" +} +`, template) +} diff --git a/azurerm/resource_arm_postgresql_configuration.go b/azurerm/resource_arm_postgresql_configuration.go index 5366fce31482..8c61c89ff437 100644 --- a/azurerm/resource_arm_postgresql_configuration.go +++ b/azurerm/resource_arm_postgresql_configuration.go @@ -1,23 +1,30 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/postgresql/mgmt/2017-12-01/postgresql" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) func resourceArmPostgreSQLConfiguration() *schema.Resource { return &schema.Resource{ - Create: resourceArmPostgreSQLConfigurationCreateUpdate, + Create: resourceArmPostgreSQLConfigurationCreate, Read: resourceArmPostgreSQLConfigurationRead, Delete: resourceArmPostgreSQLConfigurationDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Schema: map[string]*schema.Schema{ "name": { @@ -43,12 +50,14 @@ func resourceArmPostgreSQLConfiguration() *schema.Resource { } } -func resourceArmPostgreSQLConfigurationCreateUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceArmPostgreSQLConfigurationCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).postgresqlConfigurationsClient ctx := meta.(*ArmClient).StopContext log.Printf("[INFO] preparing arguments for AzureRM PostgreSQL Configuration creation.") + // NOTE: we can't require import for this resource since these settings can have default values + // and having config containing the default values would be valid. name := d.Get("name").(string) resGroup := d.Get("resource_group_name").(string) serverName := d.Get("server_name").(string) @@ -66,7 +75,9 @@ func resourceArmPostgreSQLConfigurationCreateUpdate(d *schema.ResourceData, meta return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -148,7 +159,9 @@ func resourceArmPostgreSQLConfigurationDelete(d *schema.ResourceData, meta inter return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if response.WasNotFound(future.Response()) { return nil diff --git a/azurerm/resource_arm_postgresql_database.go b/azurerm/resource_arm_postgresql_database.go index 6cd763a12749..4a4cbbaa4c65 100644 --- a/azurerm/resource_arm_postgresql_database.go +++ b/azurerm/resource_arm_postgresql_database.go @@ -1,13 +1,16 @@ package azurerm import ( + "context" "fmt" "log" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/postgresql/mgmt/2017-12-01/postgresql" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -19,6 +22,10 @@ func resourceArmPostgreSQLDatabase() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Schema: map[string]*schema.Schema{ "name": { @@ -62,6 +69,18 @@ func resourceArmPostgreSQLDatabaseCreate(d *schema.ResourceData, meta interface{ resGroup := d.Get("resource_group_name").(string) serverName := d.Get("server_name").(string) + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, serverName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of PostgreSQL Database %q (Server %q / Resource Group %q): %+v", name, serverName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_postgresql_database", *resp.ID) + } + charset := d.Get("charset").(string) collation := d.Get("collation").(string) @@ -77,7 +96,9 @@ func resourceArmPostgreSQLDatabaseCreate(d *schema.ResourceData, meta interface{ return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -154,7 +175,9 @@ func resourceArmPostgreSQLDatabaseDelete(d *schema.ResourceData, meta interface{ return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if response.WasNotFound(future.Response()) { return nil diff --git a/azurerm/resource_arm_postgresql_database_test.go b/azurerm/resource_arm_postgresql_database_test.go index ccc438b05314..91a021ff8066 100644 --- a/azurerm/resource_arm_postgresql_database_test.go +++ b/azurerm/resource_arm_postgresql_database_test.go @@ -32,6 +32,30 @@ func TestAccAzureRMPostgreSQLDatabase_basic(t *testing.T) { }) } +func TestAccAzureRMPostgreSQLDatabase_requiresImport(t *testing.T) { + resourceName := "azurerm_postgresql_database.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMPostgreSQLDatabaseDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMPostgreSQLDatabase_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMPostgreSQLDatabaseExists(resourceName), + ), + }, + { + Config: testAccAzureRMPostgreSQLDatabase_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_postgresql_database"), + }, + }, + }) +} + func TestAccAzureRMPostgreSQLDatabase_charsetLowercase(t *testing.T) { resourceName := "azurerm_postgresql_database.test" ri := acctest.RandInt() @@ -176,6 +200,21 @@ resource "azurerm_postgresql_database" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMPostgreSQLDatabase_requiresImport(rInt int, location string) string { + template := testAccAzureRMPostgreSQLDatabase_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_postgresql_database" "import" { + name = "${azurerm_postgresql_database.test.name}" + resource_group_name = "${azurerm_postgresql_database.test.resource_group_name}" + server_name = "${azurerm_postgresql_database.test.server_name}" + charset = "${azurerm_postgresql_database.test.charset}" + collation = "${azurerm_postgresql_database.test.collation}" +} +`, template) +} + func testAccAzureRMPostgreSQLDatabase_charsetLowercase(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_postgresql_firewall_rule.go b/azurerm/resource_arm_postgresql_firewall_rule.go index 98233118d07f..e24bcc1cd736 100644 --- a/azurerm/resource_arm_postgresql_firewall_rule.go +++ b/azurerm/resource_arm_postgresql_firewall_rule.go @@ -1,12 +1,15 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/postgresql/mgmt/2017-12-01/postgresql" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -18,6 +21,10 @@ func resourceArmPostgreSQLFirewallRule() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Schema: map[string]*schema.Schema{ "name": { @@ -58,6 +65,19 @@ func resourceArmPostgreSQLFirewallRuleCreate(d *schema.ResourceData, meta interf name := d.Get("name").(string) resGroup := d.Get("resource_group_name").(string) serverName := d.Get("server_name").(string) + + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, serverName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Firewall Rule %q (PostgreSQL Server %q / Resource Group %q): %+v", name, serverName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_postgresql_firewall_rule", *resp.ID) + } + startIPAddress := d.Get("start_ip_address").(string) endIPAddress := d.Get("end_ip_address").(string) @@ -73,7 +93,9 @@ func resourceArmPostgreSQLFirewallRuleCreate(d *schema.ResourceData, meta interf return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -143,7 +165,9 @@ func resourceArmPostgreSQLFirewallRuleDelete(d *schema.ResourceData, meta interf return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if response.WasNotFound(future.Response()) { return nil diff --git a/azurerm/resource_arm_postgresql_firewall_rule_test.go b/azurerm/resource_arm_postgresql_firewall_rule_test.go index 57e279e00ca3..adda567506a7 100644 --- a/azurerm/resource_arm_postgresql_firewall_rule_test.go +++ b/azurerm/resource_arm_postgresql_firewall_rule_test.go @@ -32,6 +32,30 @@ func TestAccAzureRMPostgreSQLFirewallRule_basic(t *testing.T) { }) } +func TestAccAzureRMPostgreSQLFirewallRule_requiresImport(t *testing.T) { + resourceName := "azurerm_postgresql_firewall_rule.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMPostgreSQLFirewallRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMPostgreSQLFirewallRule_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMPostgreSQLFirewallRuleExists(resourceName), + ), + }, + { + Config: testAccAzureRMPostgreSQLFirewallRule_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_postgresql_firewall_rule"), + }, + }, + }) +} + func testCheckAzureRMPostgreSQLFirewallRuleExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { // Ensure we have enough information in state to look up in API @@ -132,3 +156,18 @@ resource "azurerm_postgresql_firewall_rule" "test" { } `, rInt, location, rInt, rInt) } + +func testAccAzureRMPostgreSQLFirewallRule_requiresImport(rInt int, location string) string { + template := testAccAzureRMPostgreSQLFirewallRule_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_postgresql_firewall_rule" "import" { + name = "${azurerm_postgresql_firewall_rule.test.name}" + resource_group_name = "${azurerm_postgresql_firewall_rule.test.resource_group_name}" + server_name = "${azurerm_postgresql_firewall_rule.test.server_name}" + start_ip_address = "${azurerm_postgresql_firewall_rule.test.start_ip_address}" + end_ip_address = "${azurerm_postgresql_firewall_rule.test.end_ip_address}" +} +`, template) +} diff --git a/azurerm/resource_arm_postgresql_server.go b/azurerm/resource_arm_postgresql_server.go index 50e4ce76d163..68aca59cf047 100644 --- a/azurerm/resource_arm_postgresql_server.go +++ b/azurerm/resource_arm_postgresql_server.go @@ -1,14 +1,17 @@ package azurerm import ( + "context" "fmt" "log" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/postgresql/mgmt/2017-12-01/postgresql" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -21,6 +24,11 @@ func resourceArmPostgreSQLServer() *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": { @@ -197,9 +205,23 @@ func resourceArmPostgreSQLServerCreate(d *schema.ResourceData, meta interface{}) log.Printf("[INFO] preparing arguments for AzureRM PostgreSQL Server creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resourceGroup := d.Get("resource_group_name").(string) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of PostgreSQL Server %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_postgresql_server", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) adminLogin := d.Get("administrator_login").(string) adminLoginPassword := d.Get("administrator_login_password").(string) sslEnforcement := d.Get("ssl_enforcement").(string) @@ -229,7 +251,9 @@ func resourceArmPostgreSQLServerCreate(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error creating PostgreSQL Server %q (Resource Group %q): %+v", name, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for creation of PostgreSQL Server %q (Resource Group %q): %+v", name, resourceGroup, err) } @@ -368,7 +392,9 @@ func resourceArmPostgreSQLServerDelete(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error deleting PostgreSQL Server %q (Resource Group %q): %+v", name, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if response.WasNotFound(future.Response()) { return nil diff --git a/azurerm/resource_arm_postgresql_server_test.go b/azurerm/resource_arm_postgresql_server_test.go index 678ab110f6cd..26fcfb2f35c7 100644 --- a/azurerm/resource_arm_postgresql_server_test.go +++ b/azurerm/resource_arm_postgresql_server_test.go @@ -33,6 +33,30 @@ func TestAccAzureRMPostgreSQLServer_basicNinePointFive(t *testing.T) { }) } +func TestAccAzureRMPostgreSQLServer_requiresImport(t *testing.T) { + resourceName := "azurerm_postgresql_server.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMPostgreSQLServerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMPostgreSQLServer_basicNinePointFive(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMPostgreSQLServerExists(resourceName), + ), + }, + { + Config: testAccAzureRMPostgreSQLServer_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_postgresql_server"), + }, + }, + }) +} + func TestAccAzureRMPostgreSQLServer_basicNinePointSix(t *testing.T) { resourceName := "azurerm_postgresql_server.test" ri := acctest.RandInt() @@ -338,6 +362,37 @@ resource "azurerm_postgresql_server" "test" { `, rInt, location, rInt) } +func testAccAzureRMPostgreSQLServer_requiresImport(rInt int, location string) string { + template := testAccAzureRMPostgreSQLServer_basicNinePointFive(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_postgresql_server" "import" { + name = "${azurerm_postgresql_server.test.name}" + location = "${azurerm_postgresql_server.test.location}" + resource_group_name = "${azurerm_postgresql_server.test.resource_group_name}" + + sku { + name = "B_Gen4_2" + capacity = 2 + tier = "Basic" + family = "Gen4" + } + + storage_profile { + storage_mb = 51200 + backup_retention_days = 7 + geo_redundant_backup = "Disabled" + } + + administrator_login = "acctestun" + administrator_login_password = "H@Sh1CoR3!" + version = "9.5" + ssl_enforcement = "Enabled" +} +`, template) +} + func testAccAzureRMPostgreSQLServer_basicNinePointSix(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_postgresql_virtual_network_rule.go b/azurerm/resource_arm_postgresql_virtual_network_rule.go index 72b882dc1ac9..be4338c30660 100644 --- a/azurerm/resource_arm_postgresql_virtual_network_rule.go +++ b/azurerm/resource_arm_postgresql_virtual_network_rule.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "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/utils" ) @@ -26,6 +27,11 @@ func resourceArmPostgreSQLVirtualNetworkRule() *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": { @@ -62,6 +68,19 @@ func resourceArmPostgreSQLVirtualNetworkRuleCreateUpdate(d *schema.ResourceData, resourceGroup := d.Get("resource_group_name").(string) subnetId := d.Get("subnet_id").(string) + if d.IsNewResource() { + resp, err := client.Get(ctx, resourceGroup, serverName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Virtual Network Rule %q (Server %q / Resource Group %q): %+v", name, serverName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_postgresql_virtual_network_rule", *resp.ID) + } + } + // due to a bug in the API we have to ensure the Subnet's configured correctly or the API call will timeout // BUG: https://github.com/Azure/azure-rest-api-specs/issues/3719 subnetsClient := meta.(*ArmClient).subnetClient @@ -113,11 +132,14 @@ func resourceArmPostgreSQLVirtualNetworkRuleCreateUpdate(d *schema.ResourceData, // Wait for the provisioning state to become ready log.Printf("[DEBUG] Waiting for PostgreSQL Virtual Network Rule %q (PostgreSQL Server: %q, Resource Group: %q) to become ready: %+v", name, serverName, resourceGroup, err) + timeout := d.Timeout(tf.TimeoutForCreateUpdate(d)) + waitCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() stateConf := &resource.StateChangeConf{ Pending: []string{"Initializing", "InProgress", "Unknown", "ResponseNotFound"}, Target: []string{"Ready"}, - Refresh: postgreSQLVirtualNetworkStateStatusCodeRefreshFunc(ctx, client, resourceGroup, serverName, name), - Timeout: 10 * time.Minute, + Refresh: postgreSQLVirtualNetworkStateStatusCodeRefreshFunc(waitCtx, client, resourceGroup, serverName, name), + Timeout: timeout, MinTimeout: 1 * time.Minute, ContinuousTargetOccurence: 5, } @@ -193,7 +215,9 @@ func resourceArmPostgreSQLVirtualNetworkRuleDelete(d *schema.ResourceData, meta return fmt.Errorf("Error deleting PostgreSQL Virtual Network Rule %q (PostgreSQL Server: %q, Resource Group: %q): %+v", name, serverName, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if response.WasNotFound(future.Response()) { return nil diff --git a/azurerm/resource_arm_postgresql_virtual_network_rule_test.go b/azurerm/resource_arm_postgresql_virtual_network_rule_test.go index d860cb74c1cf..a8d080214fcb 100644 --- a/azurerm/resource_arm_postgresql_virtual_network_rule_test.go +++ b/azurerm/resource_arm_postgresql_virtual_network_rule_test.go @@ -31,6 +31,30 @@ func TestAccAzureRMPostgreSQLVirtualNetworkRule_basic(t *testing.T) { }) } +func TestAccAzureRMPostgreSQLVirtualNetworkRule_requiresImport(t *testing.T) { + resourceName := "azurerm_postgresql_virtual_network_rule.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMPostgreSQLVirtualNetworkRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMPostgreSQLVirtualNetworkRule_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMPostgreSQLVirtualNetworkRuleExists(resourceName), + ), + }, + { + Config: testAccAzureRMPostgreSQLVirtualNetworkRule_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_postgresql_virtual_network_rule"), + }, + }, + }) +} + func TestAccAzureRMPostgreSQLVirtualNetworkRule_switchSubnets(t *testing.T) { resourceName := "azurerm_postgresql_virtual_network_rule.test" ri := acctest.RandInt() @@ -261,6 +285,20 @@ resource "azurerm_postgresql_virtual_network_rule" "test" { `, rInt, location, rInt, rInt, rInt, rInt) } +func testAccAzureRMPostgreSQLVirtualNetworkRule_requiresImport(rInt int, location string) string { + template := testAccAzureRMPostgreSQLVirtualNetworkRule_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_postgresql_virtual_network_rule" "import" { + name = "${azurerm_postgresql_virtual_network_rule.test.name}" + resource_group_name = "${azurerm_postgresql_virtual_network_rule.test.resource_group_name}" + server_name = "${azurerm_postgresql_virtual_network_rule.test.server_name}" + subnet_id = "${azurerm_postgresql_virtual_network_rule.test.subnet_id}" +} +`, template) +} + func testAccAzureRMPostgreSQLVirtualNetworkRule_subnetSwitchPre(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_public_ip.go b/azurerm/resource_arm_public_ip.go index 8663880a353e..c9c3efd17099 100644 --- a/azurerm/resource_arm_public_ip.go +++ b/azurerm/resource_arm_public_ip.go @@ -1,23 +1,31 @@ package azurerm import ( + "context" "fmt" "log" "regexp" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" "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" ) func resourceArmPublicIp() *schema.Resource { return &schema.Resource{ - Create: resourceArmPublicIpCreate, + Create: resourceArmPublicIpCreateUpdate, Read: resourceArmPublicIpRead, - Update: resourceArmPublicIpCreate, + Update: resourceArmPublicIpCreateUpdate, Delete: resourceArmPublicIpDelete, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Update: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Importer: &schema.ResourceImporter{ State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { @@ -102,15 +110,30 @@ func resourceArmPublicIp() *schema.Resource { } } -func resourceArmPublicIpCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmPublicIpCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).publicIPClient ctx := meta.(*ArmClient).StopContext log.Printf("[INFO] preparing arguments for AzureRM Public IP creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_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 Public IP %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_public_ip", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) sku := network.PublicIPAddressSku{ Name: network.PublicIPAddressSkuName(d.Get("sku").(string)), } @@ -166,7 +189,9 @@ func resourceArmPublicIpCreate(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error Creating/Updating Public IP %q (Resource Group %q): %+v", name, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for completion of Public IP %q (Resource Group %q): %+v", name, resGroup, err) } @@ -259,7 +284,9 @@ func resourceArmPublicIpDelete(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error deleting Public IP %q (Resource Group %q): %+v", name, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for deletion of Public IP %q (Resource Group %q): %+v", name, resGroup, err) } diff --git a/azurerm/resource_arm_public_ip_test.go b/azurerm/resource_arm_public_ip_test.go index 72bb578004a9..0886d982c80c 100644 --- a/azurerm/resource_arm_public_ip_test.go +++ b/azurerm/resource_arm_public_ip_test.go @@ -64,6 +64,30 @@ func TestAccAzureRMPublicIpStatic_basic(t *testing.T) { }) } +func TestAccAzureRMPublicIpStatic_requiresImport(t *testing.T) { + resourceName := "azurerm_public_ip.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMPublicIpDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMPublicIPStatic_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMPublicIpExists(resourceName), + ), + }, + { + Config: testAccAzureRMPublicIPStatic_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_public_ip"), + }, + }, + }) +} + func TestAccAzureRMPublicIpStatic_basic_withDNSLabel(t *testing.T) { resourceName := "azurerm_public_ip.test" ri := acctest.RandInt() @@ -334,6 +358,20 @@ resource "azurerm_public_ip" "test" { `, rInt, location, rInt) } +func testAccAzureRMPublicIPStatic_requiresImport(rInt int, location string) string { + template := testAccAzureRMPublicIPStatic_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_public_ip" "import" { + name = "${azurerm_public_ip.test.name}" + location = "${azurerm_public_ip.test.location}" + resource_group_name = "${azurerm_public_ip.test.resource_group_name}" + public_ip_address_allocation = "${azurerm_public_ip.test.public_ip_address_allocation}" +} +`, template) +} + func testAccAzureRMPublicIPStatic_basic_withZone(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_recovery_services_vault.go b/azurerm/resource_arm_recovery_services_vault.go index bdc1860ead3e..0db31c17de88 100644 --- a/azurerm/resource_arm_recovery_services_vault.go +++ b/azurerm/resource_arm_recovery_services_vault.go @@ -1,11 +1,14 @@ package azurerm import ( + "context" "fmt" "log" "regexp" + "time" "github.com/Azure/azure-sdk-for-go/services/recoveryservices/mgmt/2016-06-01/recoveryservices" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" @@ -18,10 +21,14 @@ func resourceArmRecoveryServicesVault() *schema.Resource { Read: resourceArmRecoveryServicesVaultRead, Update: resourceArmRecoveryServicesVaultCreateUpdate, Delete: resourceArmRecoveryServicesVaultDelete, - 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": { @@ -58,13 +65,27 @@ func resourceArmRecoveryServicesVaultCreateUpdate(d *schema.ResourceData, meta i ctx := meta.(*ArmClient).StopContext name := d.Get("name").(string) - location := d.Get("location").(string) resourceGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Recovery Services Vault %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_recovery_services_vault", *resp.ID) + } + } + + location := d.Get("location").(string) tags := d.Get("tags").(map[string]interface{}) log.Printf("[DEBUG] Creating/updating Recovery Service Vault %q (resource group %q)", name, resourceGroup) - //build vault struct vault := recoveryservices.Vault{ Location: utils.String(location), Tags: expandTags(tags), @@ -74,13 +95,19 @@ func resourceArmRecoveryServicesVaultCreateUpdate(d *schema.ResourceData, meta i Properties: &recoveryservices.VaultProperties{}, } - //create recovery services vault - vault, err := client.CreateOrUpdate(ctx, resourceGroup, name, vault) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, resourceGroup, name, vault) if err != nil { return fmt.Errorf("Error creating/updating Recovery Service Vault %q (Resource Group %q): %+v", name, resourceGroup, err) } - d.SetId(*vault.ID) + read, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("Error retrieving Recovery Services Vault %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + d.SetId(*read.ID) return resourceArmRecoveryServicesVaultRead(d, meta) } @@ -137,8 +164,9 @@ func resourceArmRecoveryServicesVaultDelete(d *schema.ResourceData, meta interfa resourceGroup := id.ResourceGroup log.Printf("[DEBUG] Deleting Recovery Service Vault %q (resource group %q)", name, resourceGroup) - - resp, err := client.Delete(ctx, resourceGroup, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resourceGroup, name) if err != nil { if !utils.ResponseWasNotFound(resp) { return fmt.Errorf("Error issuing delete request for Recovery Service Vault %q (Resource Group %q): %+v", name, resourceGroup, err) diff --git a/azurerm/resource_arm_recovery_services_vault_test.go b/azurerm/resource_arm_recovery_services_vault_test.go index 7af1affe15d1..e1e16a637935 100644 --- a/azurerm/resource_arm_recovery_services_vault_test.go +++ b/azurerm/resource_arm_recovery_services_vault_test.go @@ -38,6 +38,30 @@ func TestAccAzureRMRecoveryServicesVault_basic(t *testing.T) { }) } +func TestAccAzureRMRecoveryServicesVault_requiresImport(t *testing.T) { + resourceName := "azurerm_recovery_services_vault.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMRecoveryServicesVaultDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMRecoveryServicesVault_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRecoveryServicesVaultExists(resourceName), + ), + }, + { + Config: testAccAzureRMRecoveryServicesVault_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_recovery_services_vault"), + }, + }, + }) +} + func testCheckAzureRMRecoveryServicesVaultDestroy(s *terraform.State) error { for _, rs := range s.RootModule().Resources { if rs.Type != "azurerm_recovery_services_vault" { @@ -101,14 +125,27 @@ func testAccAzureRMRecoveryServicesVault_basic(rInt int, location string) string resource "azurerm_resource_group" "test" { name = "acctestRG-%d" location = "%s" -} +} resource "azurerm_recovery_services_vault" "test" { - name = "acctest-%d" - location = "${azurerm_resource_group.test.location}" - resource_group_name = "${azurerm_resource_group.test.name}" - sku = "Standard" + name = "acctest-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "Standard" } - `, rInt, location, rInt) } + +func testAccAzureRMRecoveryServicesVault_requiresImport(rInt int, location string) string { + template := testAccAzureRMRecoveryServicesVault_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_recovery_services_vault" "import" { + name = "${azurerm_recovery_services_vault.test.name}" + location = "${azurerm_recovery_services_vault.test.location}" + resource_group_name = "${azurerm_recovery_services_vault.test.resource_group_name}" + sku = "${azurerm_recovery_services_vault.test.sku}" +} +`, template) +} diff --git a/azurerm/resource_arm_redis_cache.go b/azurerm/resource_arm_redis_cache.go index 0b390950e533..d5731736f14b 100644 --- a/azurerm/resource_arm_redis_cache.go +++ b/azurerm/resource_arm_redis_cache.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -26,6 +27,11 @@ func resourceArmRedisCache() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 60), + Update: schema.DefaultTimeout(time.Minute * 60), + Delete: schema.DefaultTimeout(time.Minute * 60), + }, Schema: map[string]*schema.Schema{ "name": { @@ -34,6 +40,7 @@ func resourceArmRedisCache() *schema.Resource { ForceNew: true, }, + // TODO: use location schema "location": { Type: schema.TypeString, Required: true, @@ -211,9 +218,21 @@ func resourceArmRedisCacheCreate(d *schema.ResourceData, meta interface{}) error log.Printf("[INFO] preparing arguments for Azure ARM Redis Cache creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(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 Redis Cache %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_redis_cache", *resp.ID) + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) enableNonSSLPort := d.Get("enable_non_ssl_port").(bool) capacity := int32(d.Get("capacity").(int)) @@ -260,7 +279,10 @@ func resourceArmRedisCacheCreate(d *schema.ResourceData, meta interface{}) error return err } - err = future.WaitForCompletionRef(ctx, client.Client) + timeout := d.Timeout(schema.TimeoutCreate) + waitCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -273,12 +295,13 @@ func resourceArmRedisCacheCreate(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("Cannot read Redis Instance %s (resource group %s) ID", name, resGroup) } + // TODO: needs confirmation - but I think this can be removed? log.Printf("[DEBUG] Waiting for Redis Instance (%s) to become available", d.Get("name")) stateConf := &resource.StateChangeConf{ Pending: []string{"Updating", "Creating"}, Target: []string{"Succeeded"}, Refresh: redisStateRefreshFunc(ctx, client, resGroup, name), - Timeout: 60 * time.Minute, + Timeout: timeout, MinTimeout: 15 * time.Second, } if _, err := stateConf.WaitForState(); err != nil { @@ -305,7 +328,6 @@ func resourceArmRedisCacheUpdate(d *schema.ResourceData, meta interface{}) error name := d.Get("name").(string) resGroup := d.Get("resource_group_name").(string) - enableNonSSLPort := d.Get("enable_non_ssl_port").(bool) capacity := int32(d.Get("capacity").(int)) @@ -339,7 +361,10 @@ func resourceArmRedisCacheUpdate(d *schema.ResourceData, meta interface{}) error parameters.RedisConfiguration = redisConfiguration } - _, err := client.Update(ctx, resGroup, name, parameters) + timeout := d.Timeout(schema.TimeoutUpdate) + waitCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + _, err := client.Update(waitCtx, resGroup, name, parameters) if err != nil { return err } @@ -352,6 +377,7 @@ func resourceArmRedisCacheUpdate(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("Cannot read Redis Instance %s (resource group %s) ID", name, resGroup) } + // TODO: confirm but I think this can be removed? log.Printf("[DEBUG] Waiting for Redis Instance (%s) to become available", d.Get("name")) stateConf := &resource.StateChangeConf{ Pending: []string{"Updating", "Creating"}, @@ -364,8 +390,6 @@ func resourceArmRedisCacheUpdate(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("Error waiting for Redis Instance (%s) to become available: %s", d.Get("name"), err) } - d.SetId(*read.ID) - patchSchedule, err := expandRedisPatchSchedule(d) if err != nil { return fmt.Errorf("Error parsing Patch Schedule: %+v", err) @@ -373,12 +397,12 @@ func resourceArmRedisCacheUpdate(d *schema.ResourceData, meta interface{}) error patchClient := meta.(*ArmClient).redisPatchSchedulesClient if patchSchedule == nil || len(*patchSchedule.ScheduleEntries.ScheduleEntries) == 0 { - _, err = patchClient.Delete(ctx, resGroup, name) + _, err = patchClient.Delete(waitCtx, resGroup, name) if err != nil { return fmt.Errorf("Error deleting Redis Patch Schedule: %+v", err) } } else { - _, err = patchClient.CreateOrUpdate(ctx, resGroup, name, *patchSchedule) + _, err = patchClient.CreateOrUpdate(waitCtx, resGroup, name, *patchSchedule) if err != nil { return fmt.Errorf("Error setting Redis Patch Schedule: %+v", err) } @@ -484,7 +508,10 @@ func resourceArmRedisCacheDelete(d *schema.ResourceData, meta interface{}) error return err } - err = future.WaitForCompletionRef(ctx, redisClient.Client) + + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, redisClient.Client) if err != nil { if response.WasNotFound(future.Response()) { return nil diff --git a/azurerm/resource_arm_redis_cache_test.go b/azurerm/resource_arm_redis_cache_test.go index f60df1aa83e3..929577b5ae82 100644 --- a/azurerm/resource_arm_redis_cache_test.go +++ b/azurerm/resource_arm_redis_cache_test.go @@ -122,6 +122,29 @@ func TestAccAzureRMRedisCache_basic(t *testing.T) { }) } +func TestAccAzureRMRedisCache_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMRedisCacheDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMRedisCache_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRedisCacheExists("azurerm_redis_cache.test"), + ), + }, + { + Config: testAccAzureRMRedisCache_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_redis_cache"), + }, + }, + }) +} + func TestAccAzureRMRedisCache_standard(t *testing.T) { ri := acctest.RandInt() config := testAccAzureRMRedisCache_standard(ri, testLocation()) @@ -469,6 +492,26 @@ resource "azurerm_redis_cache" "test" { `, rInt, location, rInt) } +func testAccAzureRMRedisCache_requiresImport(rInt int, location string) string { + template := testAccAzureRMRedisCache_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_redis_cache" "import" { + name = "${azurerm_redis_cache.test.name}" + location = "${azurerm_redis_cache.test.location}" + resource_group_name = "${azurerm_redis_cache.test.resource_group_name}" + capacity = "${azurerm_redis_cache.test.capacity}" + family = "${azurerm_redis_cache.test.family}" + sku_name = "${azurerm_redis_cache.test.sku_name}" + enable_non_ssl_port = "${azurerm_redis_cache.test.enable_non_ssl_port}" + + redis_configuration { + } +} +`, template) +} + func testAccAzureRMRedisCache_standard(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_redis_firewall_rule.go b/azurerm/resource_arm_redis_firewall_rule.go index 97fd84e9e295..0641e152523b 100644 --- a/azurerm/resource_arm_redis_firewall_rule.go +++ b/azurerm/resource_arm_redis_firewall_rule.go @@ -1,13 +1,16 @@ package azurerm import ( + "context" "fmt" "log" + "time" "regexp" "github.com/Azure/azure-sdk-for-go/services/redis/mgmt/2018-03-01/redis" "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" ) @@ -20,6 +23,11 @@ func resourceArmRedisFirewallRule() *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": { @@ -58,6 +66,21 @@ func resourceArmRedisFirewallRuleCreateUpdate(d *schema.ResourceData, meta inter name := d.Get("name").(string) cacheName := d.Get("redis_cache_name").(string) resourceGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, cacheName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Firewall Rule %q (Redis Cache %q / Resource Group %q): %+v", name, cacheName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_redis_firewall_rule", *resp.ID) + } + } + startIP := d.Get("start_ip").(string) endIP := d.Get("end_ip").(string) @@ -68,7 +91,9 @@ func resourceArmRedisFirewallRuleCreateUpdate(d *schema.ResourceData, meta inter }, } - _, err := client.CreateOrUpdate(ctx, resourceGroup, cacheName, name, parameters) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, resourceGroup, cacheName, name, parameters) if err != nil { return err } @@ -133,8 +158,9 @@ func resourceArmRedisFirewallRuleDelete(d *schema.ResourceData, meta interface{} cacheName := id.Path["Redis"] name := id.Path["firewallRules"] - resp, err := client.Delete(ctx, resourceGroup, cacheName, name) - + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resourceGroup, cacheName, name) if err != nil { if !utils.ResponseWasNotFound(resp) { return fmt.Errorf("Error issuing AzureRM delete request of Redis Firewall Rule %q (cache %q / resource group %q): %+v", name, cacheName, resourceGroup, err) diff --git a/azurerm/resource_arm_redis_firewall_rule_test.go b/azurerm/resource_arm_redis_firewall_rule_test.go index 5d50eab6234f..9bf360ae4bea 100644 --- a/azurerm/resource_arm_redis_firewall_rule_test.go +++ b/azurerm/resource_arm_redis_firewall_rule_test.go @@ -71,6 +71,30 @@ func TestAccAzureRMRedisFirewallRule_basic(t *testing.T) { }) } +func TestAccAzureRMRedisFirewallRule_requiresImport(t *testing.T) { + resourceName := "azurerm_redis_firewall_rule.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMRedisFirewallRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMRedisFirewallRule_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRedisFirewallRuleExists(resourceName), + ), + }, + { + Config: testAccAzureRMRedisFirewallRule_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_redis_firewall_rule"), + }, + }, + }) +} + func TestAccAzureRMRedisFirewallRule_update(t *testing.T) { resourceName := "azurerm_redis_firewall_rule.test" ri := acctest.RandInt() @@ -180,6 +204,21 @@ resource "azurerm_redis_firewall_rule" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMRedisFirewallRule_requiresImport(rInt int, location string) string { + template := testAccAzureRMRedisFirewallRule_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_redis_firewall_rule" "import" { + name = "${azurerm_redis_firewall_rule.test.name}" + redis_cache_name = "${azurerm_redis_firewall_rule.test.redis_cache_name}" + resource_group_name = "${azurerm_redis_firewall_rule.test.resource_group_name}" + start_ip = "${azurerm_redis_firewall_rule.test.start_ip}" + end_ip = "${azurerm_redis_firewall_rule.test.end_ip}" +} +`, template) +} + func testAccAzureRMRedisFirewallRule_update(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_relay_namespace.go b/azurerm/resource_arm_relay_namespace.go index 9a2568989453..2a944afe2b8e 100644 --- a/azurerm/resource_arm_relay_namespace.go +++ b/azurerm/resource_arm_relay_namespace.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -24,6 +25,11 @@ func resourceArmRelayNamespace() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 60), + Update: schema.DefaultTimeout(time.Minute * 60), + Delete: schema.DefaultTimeout(time.Minute * 60), + }, Schema: map[string]*schema.Schema{ "name": { @@ -95,9 +101,23 @@ func resourceArmRelayNamespaceCreateUpdate(d *schema.ResourceData, meta interfac log.Printf("[INFO] preparing arguments for Relay Namespace creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resourceGroup := d.Get("resource_group_name").(string) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Relay Namespace %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_relay_namespace", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) sku := expandRelayNamespaceSku(d) tags := d.Get("tags").(map[string]interface{}) expandedTags := expandTags(tags) @@ -114,7 +134,9 @@ func resourceArmRelayNamespaceCreateUpdate(d *schema.ResourceData, meta interfac return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -207,11 +229,14 @@ func resourceArmRelayNamespaceDelete(d *schema.ResourceData, meta interface{}) e // we can't make use of the Future here due to a bug where 404 isn't tracked as Successful log.Printf("[DEBUG] Waiting for Relay Namespace %q (Resource Group %q) to be deleted", name, resourceGroup) + timeout := d.Timeout(schema.TimeoutDelete) + waitCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() stateConf := &resource.StateChangeConf{ Pending: []string{"Pending"}, Target: []string{"Deleted"}, - Refresh: relayNamespaceDeleteRefreshFunc(ctx, client, resourceGroup, name), - Timeout: 60 * time.Minute, + Refresh: relayNamespaceDeleteRefreshFunc(waitCtx, client, resourceGroup, name), + Timeout: timeout, MinTimeout: 15 * time.Second, } if _, err := stateConf.WaitForState(); err != nil { diff --git a/azurerm/resource_arm_relay_namespace_test.go b/azurerm/resource_arm_relay_namespace_test.go index 78c169aef22e..41c546803e51 100644 --- a/azurerm/resource_arm_relay_namespace_test.go +++ b/azurerm/resource_arm_relay_namespace_test.go @@ -35,6 +35,29 @@ func TestAccAzureRMRelayNamespace_basic(t *testing.T) { }, }) } +func TestAccAzureRMRelayNamespace_requiresImport(t *testing.T) { + resourceName := "azurerm_relay_namespace.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMRelayNamespaceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMRelayNamespace_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRelayNamespaceExists(resourceName), + ), + }, + { + Config: testAccAzureRMRelayNamespace_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_relay_namespace"), + }, + }, + }) +} func TestAccAzureRMRelayNamespace_complete(t *testing.T) { resourceName := "azurerm_relay_namespace.test" @@ -134,6 +157,23 @@ resource "azurerm_relay_namespace" "test" { `, rInt, location, rInt) } +func testAccAzureRMRelayNamespace_requiresImport(rInt int, location string) string { + template := testAccAzureRMRelayNamespace_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_relay_namespace" "import" { + name = "${azurerm_relay_namespace.test.name}" + location = "${azurerm_relay_namespace.test.location}" + resource_group_name = "${azurerm_relay_namespace.test.resource_group_name}" + + sku { + name = "Standard" + } +} +`, template) +} + func testAccAzureRMRelayNamespace_complete(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_resource_group.go b/azurerm/resource_arm_resource_group.go index 92a0f1ed9672..169f9d047142 100644 --- a/azurerm/resource_arm_resource_group.go +++ b/azurerm/resource_arm_resource_group.go @@ -1,12 +1,15 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2017-05-10/resources" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -20,6 +23,13 @@ func resourceArmResourceGroup() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Update: schema.DefaultTimeout(time.Minute * 30), + // on first glance this is excessive, however since a RG can contain things unmanaged by TF + // which causes this delete to wait for those things + Delete: schema.DefaultTimeout(time.Minute * 60), + }, Schema: map[string]*schema.Schema{ "name": resourceGroupNameSchema(), @@ -36,13 +46,31 @@ func resourceArmResourceGroupCreateUpdate(d *schema.ResourceData, meta interface ctx := meta.(*ArmClient).StopContext name := d.Get("name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Resource Group %q: %+v", name, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_resource_group", *resp.ID) + } + } + location := azureRMNormalizeLocation(d.Get("location").(string)) tags := d.Get("tags").(map[string]interface{}) parameters := resources.Group{ Location: utils.String(location), Tags: expandTags(tags), } - _, err := client.CreateOrUpdate(ctx, name, parameters) + + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, name, parameters) if err != nil { return fmt.Errorf("Error creating resource group: %+v", err) } @@ -98,7 +126,6 @@ func resourceArmResourceGroupExists(d *schema.ResourceData, meta interface{}) (b } name := id.ResourceGroup - resp, err := client.Get(ctx, name) if err != nil { if utils.ResponseWasNotFound(resp.Response) { @@ -131,7 +158,9 @@ func resourceArmResourceGroupDelete(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("Error deleting Resource Group %q: %+v", name, err) } - err = deleteFuture.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = deleteFuture.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if response.WasNotFound(deleteFuture.Response()) { return nil diff --git a/azurerm/resource_arm_resource_group_test.go b/azurerm/resource_arm_resource_group_test.go index 5e5c790e1bdf..b4790c155476 100644 --- a/azurerm/resource_arm_resource_group_test.go +++ b/azurerm/resource_arm_resource_group_test.go @@ -74,6 +74,29 @@ func TestAccAzureRMResourceGroup_basic(t *testing.T) { }) } +func TestAccAzureRMResourceGroup_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMResourceGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMResourceGroup_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMResourceGroupExists("azurerm_resource_group.test"), + ), + }, + { + Config: testAccAzureRMResourceGroup_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_resource_group"), + }, + }, + }) +} + func TestAccAzureRMResourceGroup_disappears(t *testing.T) { resourceName := "azurerm_resource_group.test" ri := acctest.RandInt() @@ -217,6 +240,18 @@ resource "azurerm_resource_group" "test" { `, rInt, location) } +func testAccAzureRMResourceGroup_requiresImport(rInt int, location string) string { + template := testAccAzureRMResourceGroup_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_resource_group" "import" { + name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" +} +`, template) +} + func testAccAzureRMResourceGroup_withTags(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_role_assignment.go b/azurerm/resource_arm_role_assignment.go index 89b4c7a528f8..a2c8abea86b3 100644 --- a/azurerm/resource_arm_role_assignment.go +++ b/azurerm/resource_arm_role_assignment.go @@ -1,6 +1,7 @@ package azurerm import ( + "context" "fmt" "log" "strings" @@ -10,6 +11,7 @@ import ( "github.com/hashicorp/go-uuid" "github.com/hashicorp/terraform/helper/resource" "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" ) @@ -21,6 +23,10 @@ func resourceArmRoleAssignment() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 5), + Delete: schema.DefaultTimeout(time.Minute * 5), + }, Schema: map[string]*schema.Schema{ "name": { @@ -91,7 +97,20 @@ func resourceArmRoleAssignmentCreate(d *schema.ResourceData, meta interface{}) e principalId := d.Get("principal_id").(string) - if name == "" { + if name != "" { + // first check if there's one in this subscription requiring import + resp, err := roleAssignmentsClient.Get(ctx, scope, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Role Assignment %q: %+v", name, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_role_assignment", *resp.ID) + } + } else { + // generate a new name uuid, err := uuid.GenerateUUID() if err != nil { return fmt.Errorf("Error generating UUID for Role Assignment: %+v", err) @@ -107,7 +126,10 @@ func resourceArmRoleAssignmentCreate(d *schema.ResourceData, meta interface{}) e }, } - err := resource.Retry(300*time.Second, retryRoleAssignmentsClient(scope, name, properties, meta)) + timeout := d.Timeout(schema.TimeoutCreate) + waitCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + err := resource.Retry(timeout, retryRoleAssignmentsClient(waitCtx, scope, name, properties, meta)) if err != nil { return err } @@ -159,7 +181,9 @@ func resourceArmRoleAssignmentDelete(d *schema.ResourceData, meta interface{}) e return err } - resp, err := client.Delete(ctx, id.scope, id.name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, id.scope, id.name) if err != nil { if !utils.ResponseWasNotFound(resp.Response) { return err @@ -181,19 +205,15 @@ func validateRoleDefinitionName(i interface{}, k string) ([]string, []error) { return nil, nil } -func retryRoleAssignmentsClient(scope string, name string, properties authorization.RoleAssignmentCreateParameters, meta interface{}) func() *resource.RetryError { - +func retryRoleAssignmentsClient(ctx context.Context, scope string, name string, properties authorization.RoleAssignmentCreateParameters, meta interface{}) func() *resource.RetryError { return func() *resource.RetryError { - roleAssignmentsClient := meta.(*ArmClient).roleAssignmentsClient - ctx := meta.(*ArmClient).StopContext - - _, err := roleAssignmentsClient.Create(ctx, scope, name, properties) - + client := meta.(*ArmClient).roleAssignmentsClient + _, err := client.Create(ctx, scope, name, properties) if err != nil { return resource.RetryableError(err) } - return nil + return nil } } diff --git a/azurerm/resource_arm_role_assignment_test.go b/azurerm/resource_arm_role_assignment_test.go index 0b8fc4ebbd22..6de81274abca 100644 --- a/azurerm/resource_arm_role_assignment_test.go +++ b/azurerm/resource_arm_role_assignment_test.go @@ -16,11 +16,12 @@ func TestAccAzureRMRoleAssignment(t *testing.T) { // Azure only being happy about provisioning a couple at a time testCases := map[string]map[string]func(t *testing.T){ "basic": { - "emptyName": testAccAzureRMRoleAssignment_emptyName, - "roleName": testAccAzureRMRoleAssignment_roleName, - "dataActions": testAccAzureRMRoleAssignment_dataActions, - "builtin": testAccAzureRMRoleAssignment_builtin, - "custom": testAccAzureRMRoleAssignment_custom, + "emptyName": testAccAzureRMRoleAssignment_emptyName, + "roleName": testAccAzureRMRoleAssignment_roleName, + "dataActions": testAccAzureRMRoleAssignment_dataActions, + "builtin": testAccAzureRMRoleAssignment_builtin, + "custom": testAccAzureRMRoleAssignment_custom, + "requiresImport": testAccAzureRMRoleAssignment_requiresImport, }, "import": { "basic": testAccAzureRMRoleAssignment_importBasic, @@ -82,6 +83,29 @@ func testAccAzureRMRoleAssignment_roleName(t *testing.T) { }) } +func testAccAzureRMRoleAssignment_requiresImport(t *testing.T) { + id := uuid.New().String() + resourceName := "azurerm_role_assignment.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMRoleAssignmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMRoleAssignment_roleNameConfig(id), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRoleAssignmentExists(resourceName), + ), + }, + { + Config: testAccAzureRMRoleAssignment_requiresImportConfig(id), + ExpectError: testRequiresImportError("azurerm_role_assignment"), + }, + }, + }) +} + func testAccAzureRMRoleAssignment_dataActions(t *testing.T) { id := uuid.New().String() resourceName := "azurerm_role_assignment.test" @@ -228,6 +252,20 @@ resource "azurerm_role_assignment" "test" { `, id) } +func testAccAzureRMRoleAssignment_requiresImportConfig(id string) string { + template := testAccAzureRMRoleAssignment_roleNameConfig(id) + return fmt.Sprintf(` +%s + +resource "azurerm_role_assignment" "import" { + name = "${azurerm_role_assignment.test.name}" + scope = "${azurerm_role_assignment.test.scope}" + role_definition_name = "${azurerm_role_assignment.test.role_definition_name}" + principal_id = "${azurerm_role_assignment.test.principal_id}" +} +`, template) +} + func testAccAzureRMRoleAssignment_dataActionsConfig(id string) string { return fmt.Sprintf(` data "azurerm_subscription" "primary" {} diff --git a/azurerm/resource_arm_role_definition.go b/azurerm/resource_arm_role_definition.go index 81ad435c2f9f..7eb6cfaffc36 100644 --- a/azurerm/resource_arm_role_definition.go +++ b/azurerm/resource_arm_role_definition.go @@ -1,13 +1,16 @@ package azurerm import ( + "context" "fmt" "log" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/authorization/mgmt/2018-01-01-preview/authorization" "github.com/hashicorp/go-uuid" "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" ) @@ -20,6 +23,11 @@ func resourceArmRoleDefinition() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 10), + Update: schema.DefaultTimeout(time.Minute * 10), + Delete: schema.DefaultTimeout(time.Minute * 10), + }, Schema: map[string]*schema.Schema{ "role_definition_id": { @@ -83,18 +91,32 @@ func resourceArmRoleDefinitionCreateUpdate(d *schema.ResourceData, meta interfac client := meta.(*ArmClient).roleDefinitionsClient ctx := meta.(*ArmClient).StopContext + scope := d.Get("scope").(string) roleDefinitionId := d.Get("role_definition_id").(string) - if roleDefinitionId == "" { + if roleDefinitionId != "" { + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, scope, roleDefinitionId) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Role Definition ID %q (Scope %q): %+v", roleDefinitionId, scope, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_role_definition", *resp.ID) + } + } + } else { uuid, err := uuid.GenerateUUID() if err != nil { - return fmt.Errorf("Error generating UUID for Role Assignment: %+v", err) + return fmt.Errorf("Error generating UUID for Role Definition: %+v", err) } roleDefinitionId = uuid } name := d.Get("name").(string) - scope := d.Get("scope").(string) description := d.Get("description").(string) roleType := "CustomRole" permissions := expandRoleDefinitionPermissions(d) @@ -110,7 +132,9 @@ func resourceArmRoleDefinitionCreateUpdate(d *schema.ResourceData, meta interfac }, } - _, err := client.CreateOrUpdate(ctx, scope, roleDefinitionId, properties) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, scope, roleDefinitionId, properties) if err != nil { return err } @@ -169,7 +193,9 @@ func resourceArmRoleDefinitionDelete(d *schema.ResourceData, meta interface{}) e return err } - resp, err := client.Delete(ctx, id.scope, id.roleDefinitionId) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, id.scope, id.roleDefinitionId) if err != nil { if !utils.ResponseWasNotFound(resp.Response) { return fmt.Errorf("Error deleting Role Definition %q at Scope %q: %+v", id.roleDefinitionId, id.scope, err) diff --git a/azurerm/resource_arm_role_definition_test.go b/azurerm/resource_arm_role_definition_test.go index 3c72499a6f0a..6ff7de4cfc0f 100644 --- a/azurerm/resource_arm_role_definition_test.go +++ b/azurerm/resource_arm_role_definition_test.go @@ -49,6 +49,29 @@ func TestAccAzureRMRoleDefinition_complete(t *testing.T) { }) } +func TestAccAzureRMRoleDefinition_requiresImport(t *testing.T) { + ri := acctest.RandInt() + id := uuid.New().String() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMRoleDefinitionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMRoleDefinition_complete(id, ri), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRoleDefinitionExists("azurerm_role_definition.test"), + ), + }, + { + Config: testAccAzureRMRoleDefinition_requiresImport(id, ri), + ExpectError: testRequiresImportError("azurerm_role_definition"), + }, + }, + }) +} + func TestAccAzureRMRoleDefinition_update(t *testing.T) { resourceName := "azurerm_role_definition.test" id := uuid.New().String() @@ -202,6 +225,29 @@ resource "azurerm_role_definition" "test" { `, id, rInt) } +func testAccAzureRMRoleDefinition_requiresImport(id string, rInt int) string { + template := testAccAzureRMRoleDefinition_complete(id, rInt) + return fmt.Sprintf(` +%s + +resource "azurerm_role_definition" "import" { + role_definition_id = "${azurerm_role_definition.test.role_definition_id}" + name = "${azurerm_role_definition.test.name}" + scope = "${azurerm_role_definition.test.scope}" + description = "${azurerm_role_definition.test.description}" + + permissions { + actions = ["*"] + not_actions = ["Microsoft.Authorization/*/read"] + } + + assignable_scopes = [ + "${data.azurerm_subscription.primary.id}", + ] +} +`, template) +} + func testAccAzureRMRoleDefinition_updated(id string, rInt int) string { return fmt.Sprintf(` data "azurerm_subscription" "primary" {} diff --git a/azurerm/resource_arm_route.go b/azurerm/resource_arm_route.go index b5f889f52a85..e22fb5421328 100644 --- a/azurerm/resource_arm_route.go +++ b/azurerm/resource_arm_route.go @@ -1,12 +1,15 @@ package azurerm import ( + "context" "fmt" + "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "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/utils" ) @@ -20,6 +23,11 @@ func resourceArmRoute() *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": { @@ -73,6 +81,20 @@ func resourceArmRouteCreateUpdate(d *schema.ResourceData, meta interface{}) erro rtName := d.Get("route_table_name").(string) resGroup := d.Get("resource_group_name").(string) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, rtName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Route %q (Route Table %q / Resource Group %q): %+v", name, rtName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_route", *resp.ID) + } + } + addressPrefix := d.Get("address_prefix").(string) nextHopType := d.Get("next_hop_type").(string) @@ -96,7 +118,10 @@ func resourceArmRouteCreateUpdate(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("Error Creating/Updating Route %q (Route Table %q / Resource Group %q): %+v", name, rtName, resGroup, err) } - if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) + if err != nil { return fmt.Errorf("Error waiting for completion for Route %q (Route Table %q / Resource Group %q): %+v", name, rtName, resGroup, err) } @@ -169,7 +194,9 @@ func resourceArmRouteDelete(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error deleting Route %q (Route Table %q / Resource Group %q): %+v", routeName, rtName, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for deletion of Route %q (Route Table %q / Resource Group %q): %+v", routeName, rtName, resGroup, err) } diff --git a/azurerm/resource_arm_route_table.go b/azurerm/resource_arm_route_table.go index 773193a47a0f..bf37b4b61a7f 100644 --- a/azurerm/resource_arm_route_table.go +++ b/azurerm/resource_arm_route_table.go @@ -1,14 +1,17 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" "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/utils" ) @@ -20,10 +23,14 @@ func resourceArmRouteTable() *schema.Resource { Read: resourceArmRouteTableRead, Update: resourceArmRouteTableCreateUpdate, Delete: resourceArmRouteTableDelete, - 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": { @@ -103,8 +110,23 @@ func resourceArmRouteTableCreateUpdate(d *schema.ResourceData, meta interface{}) log.Printf("[INFO] preparing arguments for AzureRM Route Table creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_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 Route Table %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_route_table", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) tags := d.Get("tags").(map[string]interface{}) routeSet := network.RouteTable{ @@ -122,7 +144,10 @@ func resourceArmRouteTableCreateUpdate(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error Creating/Updating Route Table %q (Resource Group %q): %+v", name, resGroup, err) } - if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) + if err != nil { return fmt.Errorf("Error waiting for completion of Route Table %q (Resource Group %q): %+v", name, resGroup, err) } @@ -199,7 +224,10 @@ func resourceArmRouteTableDelete(d *schema.ResourceData, meta interface{}) error } } - if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) + if err != nil { return fmt.Errorf("Error waiting for deletion of Route Table %q (Resource Group %q): %+v", name, resGroup, err) } diff --git a/azurerm/resource_arm_route_table_test.go b/azurerm/resource_arm_route_table_test.go index 634c35fda922..570ab5e08212 100644 --- a/azurerm/resource_arm_route_table_test.go +++ b/azurerm/resource_arm_route_table_test.go @@ -32,6 +32,30 @@ func TestAccAzureRMRouteTable_basic(t *testing.T) { }) } +func TestAccAzureRMRouteTable_requiresImport(t *testing.T) { + resourceName := "azurerm_route_table.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMRouteTableDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMRouteTable_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRouteTableExists(resourceName), + ), + }, + { + Config: testAccAzureRMRouteTable_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_route_table"), + }, + }, + }) +} + func TestAccAzureRMRouteTable_complete(t *testing.T) { resourceName := "azurerm_route_table.test" ri := acctest.RandInt() @@ -367,6 +391,19 @@ resource "azurerm_route_table" "test" { `, rInt, location, rInt) } +func testAccAzureRMRouteTable_requiresImport(rInt int, location string) string { + template := testAccAzureRMRouteTable_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_route_table" "import" { + name = "${azurerm_route_table.test.name}" + location = "${azurerm_route_table.test.location}" + resource_group_name = "${azurerm_route_table.test.resource_group_name}" +} +`, template) +} + func testAccAzureRMRouteTable_complete(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_route_test.go b/azurerm/resource_arm_route_test.go index 0c1acc978f64..834c6f77b4cc 100644 --- a/azurerm/resource_arm_route_test.go +++ b/azurerm/resource_arm_route_test.go @@ -35,6 +35,29 @@ func TestAccAzureRMRoute_basic(t *testing.T) { }) } +func TestAccAzureRMRoute_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMRoute_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRouteExists("azurerm_route.test"), + ), + }, + { + Config: testAccAzureRMRoute_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_route"), + }, + }, + }) +} + func TestAccAzureRMRoute_disappears(t *testing.T) { ri := acctest.RandInt() config := testAccAzureRMRoute_basic(ri, testLocation()) @@ -197,6 +220,21 @@ resource "azurerm_route" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMRoute_requiresImport(rInt int, location string) string { + template := testAccAzureRMRoute_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_route" "import" { + name = "${azurerm_route.test.name}" + resource_group_name = "${azurerm_route.test.resource_group_name}" + route_table_name = "${azurerm_route.test.route_table_name}" + address_prefix = "${azurerm_route.test.address_prefix}" + next_hop_type = "${azurerm_route.test.next_hop_type}" +} +`, template) +} + func testAccAzureRMRoute_multipleRoutes(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_scheduler_job.go b/azurerm/resource_arm_scheduler_job.go index b0858d0d414b..f0121de23c72 100644 --- a/azurerm/resource_arm_scheduler_job.go +++ b/azurerm/resource_arm_scheduler_job.go @@ -2,6 +2,7 @@ package azurerm import ( "bytes" + "context" "fmt" "log" "regexp" @@ -10,6 +11,7 @@ import ( "github.com/Azure/azure-sdk-for-go/services/scheduler/mgmt/2016-03-01/scheduler" "github.com/Azure/go-autorest/autorest/date" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" @@ -27,12 +29,15 @@ func resourceArmSchedulerJob() *schema.Resource { Read: resourceArmSchedulerJobRead, Update: resourceArmSchedulerJobCreateUpdate, Delete: resourceArmSchedulerJobDelete, - Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, - CustomizeDiff: resourceArmSchedulerJobCustomizeDiff, + 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": { @@ -479,6 +484,20 @@ func resourceArmSchedulerJobCreateUpdate(d *schema.ResourceData, meta interface{ resourceGroup := d.Get("resource_group_name").(string) jobCollection := d.Get("job_collection_name").(string) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, jobCollection, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Scheduler Job %q (Job Collection %q / Resource Group %q): %+v", name, jobCollection, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_scheduler_job", *resp.ID) + } + } + job := scheduler.JobDefinition{ Properties: &scheduler.JobProperties{ Action: expandAzureArmSchedulerJobAction(d, meta), @@ -505,7 +524,9 @@ func resourceArmSchedulerJobCreateUpdate(d *schema.ResourceData, meta interface{ job.Properties.State = scheduler.JobState(state.(string)) } - resp, err := client.CreateOrUpdate(ctx, resourceGroup, jobCollection, name, job) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + resp, err := client.CreateOrUpdate(waitCtx, resourceGroup, jobCollection, name, job) if err != nil { return fmt.Errorf("Error creating/updating Scheduler Job %q (Resource Group %q): %+v", name, resourceGroup, err) } @@ -624,7 +645,9 @@ func resourceArmSchedulerJobDelete(d *schema.ResourceData, meta interface{}) err log.Printf("[DEBUG] Deleting Scheduler Job %q (resource group %q)", name, resourceGroup) - resp, err := client.Delete(ctx, resourceGroup, jobCollection, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resourceGroup, jobCollection, name) if err != nil { if !utils.ResponseWasNotFound(resp) { return fmt.Errorf("Error issuing delete request for Scheduler Job %q (Resource Group %q): %+v", name, resourceGroup, err) diff --git a/azurerm/resource_arm_scheduler_job_collection.go b/azurerm/resource_arm_scheduler_job_collection.go index 7227fa60d230..10254265d892 100644 --- a/azurerm/resource_arm_scheduler_job_collection.go +++ b/azurerm/resource_arm_scheduler_job_collection.go @@ -1,11 +1,14 @@ package azurerm import ( + "context" "fmt" "log" "regexp" + "time" "github.com/Azure/azure-sdk-for-go/services/scheduler/mgmt/2016-03-01/scheduler" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" @@ -19,10 +22,14 @@ func resourceArmSchedulerJobCollection() *schema.Resource { Read: resourceArmSchedulerJobCollectionRead, Update: resourceArmSchedulerJobCollectionCreateUpdate, Delete: resourceArmSchedulerJobCollectionDelete, - 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": { @@ -121,8 +128,23 @@ func resourceArmSchedulerJobCollectionCreateUpdate(d *schema.ResourceData, meta ctx := meta.(*ArmClient).StopContext name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resourceGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Scheduler Job Collection %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_scheduler_job_collection", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) tags := d.Get("tags").(map[string]interface{}) log.Printf("[DEBUG] Creating/updating Scheduler Job Collection %q (resource group %q)", name, resourceGroup) @@ -143,7 +165,9 @@ func resourceArmSchedulerJobCollectionCreateUpdate(d *schema.ResourceData, meta collection.Properties.Quota = expandAzureArmSchedulerJobCollectionQuota(d) //create job collection - collection, err := client.CreateOrUpdate(ctx, resourceGroup, name, collection) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + collection, err := client.CreateOrUpdate(waitCtx, resourceGroup, name, collection) if err != nil { return fmt.Errorf("Error creating/updating Scheduler Job Collection %q (Resource Group %q): %+v", name, resourceGroup, err) } @@ -227,7 +251,9 @@ func resourceArmSchedulerJobCollectionDelete(d *schema.ResourceData, meta interf } } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if !response.WasNotFound(future.Response()) { return fmt.Errorf("Error waiting for deletion of Scheduler Job Collection %q (Resource Group %q): %+v", name, resourceGroup, err) diff --git a/azurerm/resource_arm_scheduler_job_collection_test.go b/azurerm/resource_arm_scheduler_job_collection_test.go index 08922ae1d9f8..25214f99a000 100644 --- a/azurerm/resource_arm_scheduler_job_collection_test.go +++ b/azurerm/resource_arm_scheduler_job_collection_test.go @@ -13,8 +13,9 @@ import ( ) func TestAccAzureRMSchedulerJobCollection_basic(t *testing.T) { - ri := acctest.RandInt() resourceName := "azurerm_scheduler_job_collection.test" + ri := acctest.RandInt() + location := testLocation() resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -22,7 +23,7 @@ func TestAccAzureRMSchedulerJobCollection_basic(t *testing.T) { CheckDestroy: testCheckAzureRMSchedulerJobCollectionDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMSchedulerJobCollection_basic(ri, testLocation(), ""), + Config: testAccAzureRMSchedulerJobCollection_basic(ri, location), Check: checkAccAzureRMSchedulerJobCollection_basic(resourceName), }, { @@ -34,9 +35,32 @@ func TestAccAzureRMSchedulerJobCollection_basic(t *testing.T) { }) } -func TestAccAzureRMSchedulerJobCollection_complete(t *testing.T) { +func TestAccAzureRMSchedulerJobCollection_requiresImport(t *testing.T) { + resourceName := "azurerm_scheduler_job_collection.test" ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSchedulerJobCollectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSchedulerJobCollection_basic(ri, location), + Check: checkAccAzureRMSchedulerJobCollection_basic(resourceName), + }, + { + Config: testAccAzureRMSchedulerJobCollection_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_scheduler_job_collection"), + }, + }, + }) +} + +func TestAccAzureRMSchedulerJobCollection_complete(t *testing.T) { resourceName := "azurerm_scheduler_job_collection.test" + ri := acctest.RandInt() + location := testLocation() resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -44,11 +68,11 @@ func TestAccAzureRMSchedulerJobCollection_complete(t *testing.T) { CheckDestroy: testCheckAzureRMSchedulerJobCollectionDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMSchedulerJobCollection_basic(ri, testLocation(), ""), + Config: testAccAzureRMSchedulerJobCollection_basic(ri, location), Check: checkAccAzureRMSchedulerJobCollection_basic(resourceName), }, { - Config: testAccAzureRMSchedulerJobCollection_complete(ri, testLocation()), + Config: testAccAzureRMSchedulerJobCollection_complete(ri, location), Check: checkAccAzureRMSchedulerJobCollection_complete(resourceName), }, { @@ -118,32 +142,56 @@ func testCheckAzureRMSchedulerJobCollectionExists(name string) resource.TestChec } } -func testAccAzureRMSchedulerJobCollection_basic(rInt int, location string, additional string) string { +func testAccAzureRMSchedulerJobCollection_basic(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { name = "acctestRG-%d" location = "%s" -} +} resource "azurerm_scheduler_job_collection" "test" { name = "acctest-%d" location = "${azurerm_resource_group.test.location}" resource_group_name = "${azurerm_resource_group.test.name}" - sku = "Standard" -%s -} -`, rInt, location, rInt, additional) + sku = "Standard" +} +`, rInt, location, rInt) +} + +func testAccAzureRMSchedulerJobCollection_requiresImport(rInt int, location string) string { + template := testAccAzureRMSchedulerJobCollection_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_scheduler_job_collection" "import" { + name = "${azurerm_scheduler_job_collection.test.name}" + location = "${azurerm_scheduler_job_collection.test.location}" + resource_group_name = "${azurerm_scheduler_job_collection.test.resource_group_name}" + sku = "${azurerm_scheduler_job_collection.test.sku}" +} +`, template) } func testAccAzureRMSchedulerJobCollection_complete(rInt int, location string) string { - return testAccAzureRMSchedulerJobCollection_basic(rInt, location, ` - state = "disabled" + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_scheduler_job_collection" "test" { + name = "acctest-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + state = "disabled" + quota { max_recurrence_frequency = "Hour" max_recurrence_interval = 10 max_job_count = 10 } -`) +} +`, rInt, location, rInt) } func checkAccAzureRMSchedulerJobCollection_basic(resourceName string) resource.TestCheckFunc { diff --git a/azurerm/resource_arm_scheduler_job_test.go b/azurerm/resource_arm_scheduler_job_test.go index 189846f060d7..9ede711e6f1e 100644 --- a/azurerm/resource_arm_scheduler_job_test.go +++ b/azurerm/resource_arm_scheduler_job_test.go @@ -38,6 +38,30 @@ func TestAccAzureRMSchedulerJob_web_basic(t *testing.T) { }) } +func TestAccAzureRMSchedulerJob_requiresImport(t *testing.T) { + resourceName := "azurerm_scheduler_job.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSchedulerJobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSchedulerJob_web_basic(ri, location), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMSchedulerJobExists(resourceName), + ), + }, + { + Config: testAccAzureRMSchedulerJob_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_scheduler_job"), + }, + }, + }) +} + func TestAccAzureRMSchedulerJob_storageQueue(t *testing.T) { resourceName := "azurerm_scheduler_job.test" ri := acctest.RandInt() @@ -551,7 +575,9 @@ resource "azurerm_scheduler_job_collection" "test" { func testAccAzureRMSchedulerJob_web_basic(rInt int, location string) string { //need a valid URL here otherwise on a slow connection job might fault before the test check - return fmt.Sprintf(`%s + return fmt.Sprintf(` +%s + resource "azurerm_scheduler_job" "test" { name = "acctest-%d-job" resource_group_name = "${azurerm_resource_group.test.name}" @@ -561,7 +587,26 @@ resource "azurerm_scheduler_job" "test" { url = "http://example.com" method = "get" } -}`, testAccAzureRMSchedulerJob_template(rInt, location), rInt) +} +`, testAccAzureRMSchedulerJob_template(rInt, location), rInt) +} + +func testAccAzureRMSchedulerJob_requiresImport(rInt int, location string) string { + template := testAccAzureRMSchedulerJob_web_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_scheduler_job" "import" { + name = "${azurerm_scheduler_job.test.name}" + resource_group_name = "${azurerm_scheduler_job.test.resource_group_name}" + job_collection_name = "${azurerm_scheduler_job.test.job_collection_name}" + + action_web { + url = "http://example.com" + method = "get" + } +} +`, template) } func testAccAzureRMSchedulerJob_web_put(rInt int, location string) string { diff --git a/azurerm/resource_arm_search_service.go b/azurerm/resource_arm_search_service.go index a0cfeca09c91..cd4d2d0a69f1 100644 --- a/azurerm/resource_arm_search_service.go +++ b/azurerm/resource_arm_search_service.go @@ -1,12 +1,15 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/search/mgmt/2015-08-19/search" "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" ) @@ -18,6 +21,10 @@ func resourceArmSearchService() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Schema: map[string]*schema.Schema{ "name": { @@ -68,8 +75,21 @@ func resourceArmSearchServiceCreateUpdate(d *schema.ResourceData, meta interface ctx := meta.(*ArmClient).StopContext name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name, nil) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Search Service %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_search_service", *resp.ID) + } + location := azureRMNormalizeLocation(d.Get("location").(string)) - resourceGroupName := d.Get("resource_group_name").(string) skuName := d.Get("sku").(string) tags := d.Get("tags").(map[string]interface{}) @@ -92,12 +112,14 @@ func resourceArmSearchServiceCreateUpdate(d *schema.ResourceData, meta interface properties.ServiceProperties.PartitionCount = utils.Int32(partitionCount) } - _, err := client.CreateOrUpdate(ctx, resourceGroupName, name, properties, nil) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + _, err = client.CreateOrUpdate(waitCtx, resourceGroup, name, properties, nil) if err != nil { return err } - resp, err := client.Get(ctx, resourceGroupName, name, nil) + resp, err = client.Get(ctx, resourceGroup, name, nil) if err != nil { return err } @@ -165,8 +187,9 @@ func resourceArmSearchServiceDelete(d *schema.ResourceData, meta interface{}) er resourceGroup := id.ResourceGroup name := id.Path["searchServices"] - resp, err := client.Delete(ctx, resourceGroup, name, nil) - + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resourceGroup, name, nil) if err != nil { if utils.ResponseWasNotFound(resp) { return nil diff --git a/azurerm/resource_arm_search_service_test.go b/azurerm/resource_arm_search_service_test.go index 7982a8e14ece..881aa8f4741e 100644 --- a/azurerm/resource_arm_search_service_test.go +++ b/azurerm/resource_arm_search_service_test.go @@ -31,6 +31,30 @@ func TestAccAzureRMSearchService_basic(t *testing.T) { }) } +func TestAccAzureRMSearchService_requiresImport(t *testing.T) { + resourceName := "azurerm_search_service.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSearchServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSearchService_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSearchServiceExists(resourceName), + ), + }, + { + Config: testAccAzureRMSearchService_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_search_service"), + }, + }, + }) +} + func TestAccAzureRMSearchService_complete(t *testing.T) { resourceName := "azurerm_search_service.test" ri := acctest.RandInt() @@ -129,6 +153,24 @@ resource "azurerm_search_service" "test" { `, rInt, location, rInt) } +func testAccAzureRMSearchService_requiresImport(rInt int, location string) string { + template := testAccAzureRMSearchService_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_search_service" "import" { + name = "${azurerm_search_service.test.name}" + resource_group_name = "${azurerm_search_service.test.resource_group_name}" + location = "${azurerm_search_service.test.location}" + sku = "standard" + + tags { + environment = "staging" + } +} +`, template) +} + func testAccAzureRMSearchService_complete(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_service_fabric_cluster.go b/azurerm/resource_arm_service_fabric_cluster.go index 8ddd315ad51a..ccf0c61bde60 100644 --- a/azurerm/resource_arm_service_fabric_cluster.go +++ b/azurerm/resource_arm_service_fabric_cluster.go @@ -1,13 +1,16 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/servicefabric/mgmt/2018-02-01/servicefabric" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -20,6 +23,11 @@ func resourceArmServiceFabricCluster() *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": { @@ -271,6 +279,19 @@ func resourceArmServiceFabricClusterCreate(d *schema.ResourceData, meta interfac resourceGroup := d.Get("resource_group_name").(string) name := d.Get("name").(string) + + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Service Fabric Cluster %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_service_fabric_cluster", *resp.ID) + } + location := d.Get("location").(string) reliabilityLevel := d.Get("reliability_level").(string) managementEndpoint := d.Get("management_endpoint").(string) @@ -318,7 +339,9 @@ func resourceArmServiceFabricClusterCreate(d *schema.ResourceData, meta interfac return fmt.Errorf("Error creating Service Fabric Cluster %q (Resource Group %q): %+v", name, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for creation of Service Fabric Cluster %q (Resource Group %q): %+v", name, resourceGroup, err) } @@ -381,7 +404,9 @@ func resourceArmServiceFabricClusterUpdate(d *schema.ResourceData, meta interfac return fmt.Errorf("Error updating Service Fabric Cluster %q (Resource Group %q): %+v", name, resourceGroup, 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 fmt.Errorf("Error waiting for update of Service Fabric Cluster %q (Resource Group %q): %+v", name, resourceGroup, err) } @@ -474,7 +499,9 @@ func resourceArmServiceFabricClusterDelete(d *schema.ResourceData, meta interfac log.Printf("[DEBUG] Deleting Service Fabric Cluster %q (Resource Group %q)", name, resourceGroup) - resp, err := client.Delete(ctx, resourceGroup, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resourceGroup, name) if err != nil { if !response.WasNotFound(resp.Response) { return fmt.Errorf("Error deleting Service Fabric Cluster %q (Resource Group %q): %+v", name, resourceGroup, err) diff --git a/azurerm/resource_arm_service_fabric_cluster_test.go b/azurerm/resource_arm_service_fabric_cluster_test.go index 439eea3a56fa..b8636fdfc6bc 100644 --- a/azurerm/resource_arm_service_fabric_cluster_test.go +++ b/azurerm/resource_arm_service_fabric_cluster_test.go @@ -44,6 +44,29 @@ func TestAccAzureRMServiceFabricCluster_basic(t *testing.T) { }) } +func TestAccAzureRMServiceFabricCluster_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMServiceFabricClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMServiceFabricCluster_basic(ri, location, 3), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMServiceFabricClusterExists("azurerm_service_fabric_cluster.test"), + ), + }, + { + Config: testAccAzureRMServiceFabricCluster_requiresImport(ri, location, 3), + ExpectError: testRequiresImportError("azurerm_service_fabric_cluster"), + }, + }, + }) +} + func TestAccAzureRMServiceFabricCluster_addOnFeatures(t *testing.T) { resourceName := "azurerm_service_fabric_cluster.test" ri := acctest.RandInt() @@ -432,6 +455,31 @@ resource "azurerm_service_fabric_cluster" "test" { `, rInt, location, rInt, count) } +func testAccAzureRMServiceFabricCluster_requiresImport(rInt int, location string, count int) string { + template := testAccAzureRMServiceFabricCluster_basic(rInt, location, count) + return fmt.Sprintf(` +%s + +resource "azurerm_service_fabric_cluster" "import" { + name = "${azurerm_service_fabric_cluster.test.name}" + resource_group_name = "${azurerm_service_fabric_cluster.test.resource_group_name}" + location = "${azurerm_service_fabric_cluster.test.location}" + reliability_level = "${azurerm_service_fabric_cluster.test.reliability_level}" + upgrade_mode = "${azurerm_service_fabric_cluster.test.upgrade_mode}" + vm_image = "${azurerm_service_fabric_cluster.test.vm_image}" + management_endpoint = "${azurerm_service_fabric_cluster.test.management_endpoint}" + + node_type { + name = "${azurerm_service_fabric_cluster.test.node_type.0.name}" + instance_count = "${azurerm_service_fabric_cluster.test.node_type.0.instance_count}" + is_primary = "${azurerm_service_fabric_cluster.test.node_type.0.is_primary}" + client_endpoint_port = "${azurerm_service_fabric_cluster.test.node_type.0.client_endpoint_port}" + http_endpoint_port = "${azurerm_service_fabric_cluster.test.node_type.0.http_endpoint_port}" + } +} +`, template) +} + func testAccAzureRMServiceFabricCluster_addOnFeatures(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_servicebus_namespace.go b/azurerm/resource_arm_servicebus_namespace.go index f53f4001c92f..33c5121ab446 100644 --- a/azurerm/resource_arm_servicebus_namespace.go +++ b/azurerm/resource_arm_servicebus_namespace.go @@ -1,15 +1,18 @@ package azurerm import ( + "context" "fmt" "log" "regexp" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/servicebus/mgmt/2017-04-01/servicebus" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -19,11 +22,15 @@ var serviceBusNamespaceDefaultAuthorizationRule = "RootManageSharedAccessKey" func resourceArmServiceBusNamespace() *schema.Resource { return &schema.Resource{ - Create: resourceArmServiceBusNamespaceCreate, + Create: resourceArmServiceBusNamespaceCreateUpdate, Read: resourceArmServiceBusNamespaceRead, - Update: resourceArmServiceBusNamespaceCreate, + Update: resourceArmServiceBusNamespaceCreateUpdate, Delete: resourceArmServiceBusNamespaceDelete, - + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Update: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -107,18 +114,31 @@ func resourceArmServiceBusNamespace() *schema.Resource { } } -func resourceArmServiceBusNamespaceCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmServiceBusNamespaceCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).serviceBusNamespacesClient ctx := meta.(*ArmClient).StopContext log.Printf("[INFO] preparing arguments for AzureRM ServiceBus Namespace creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resourceGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Service Bus Namespace %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_servicebus_namespace", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) sku := d.Get("sku").(string) tags := d.Get("tags").(map[string]interface{}) - parameters := servicebus.SBNamespace{ Location: &location, Sku: &servicebus.SBSku{ @@ -137,21 +157,21 @@ func resourceArmServiceBusNamespaceCreate(d *schema.ResourceData, meta interface return err } - err = future.WaitForCompletionRef(ctx, client.Client) - if err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + if err = future.WaitForCompletionRef(waitCtx, client.Client); err != nil { return err } - read, err := client.Get(ctx, resourceGroup, name) + resp, err := client.Get(ctx, resourceGroup, name) if err != nil { return err } - - if read.ID == nil { + if resp.ID == nil { return fmt.Errorf("Cannot read ServiceBus Namespace %q (resource group %q) ID", name, resourceGroup) } - d.SetId(*read.ID) + d.SetId(*resp.ID) return resourceArmServiceBusNamespaceRead(d, meta) } @@ -173,7 +193,7 @@ func resourceArmServiceBusNamespaceRead(d *schema.ResourceData, meta interface{} d.SetId("") return nil } - return fmt.Errorf("Error making Read request on Azure ServiceBus Namespace %q: %+v", name, err) + return fmt.Errorf("Error making Read request on ServiceBus Namespace %q: %+v", name, err) } d.Set("name", resp.Name) @@ -213,17 +233,21 @@ func resourceArmServiceBusNamespaceDelete(d *schema.ResourceData, meta interface resourceGroup := id.ResourceGroup name := id.Path["namespaces"] - future, err := client.Delete(ctx, resourceGroup, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + future, err := client.Delete(waitCtx, resourceGroup, name) if err != nil { - return err - } - - if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { if response.WasNotFound(future.Response()) { return nil } - return fmt.Errorf("Error deleting Service Bus %q: %+v", name, err) + return fmt.Errorf("Error deleting ServiceBus Namespace %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + if !response.WasNotFound(future.Response()) { + return fmt.Errorf("Error waiting for the deletion of ServiceBus Namespace %q (Resource Group %q): %+v", name, resourceGroup, err) + } } return nil diff --git a/azurerm/resource_arm_servicebus_namespace_authorization_rule.go b/azurerm/resource_arm_servicebus_namespace_authorization_rule.go index bd1ed46b5806..754f21da7fbf 100644 --- a/azurerm/resource_arm_servicebus_namespace_authorization_rule.go +++ b/azurerm/resource_arm_servicebus_namespace_authorization_rule.go @@ -1,12 +1,15 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/servicebus/mgmt/2017-04-01/servicebus" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -16,7 +19,11 @@ func resourceArmServiceBusNamespaceAuthorizationRule() *schema.Resource { Read: resourceArmServiceBusNamespaceAuthorizationRuleRead, Update: resourceArmServiceBusNamespaceAuthorizationRuleCreateUpdate, Delete: resourceArmServiceBusNamespaceAuthorizationRuleDelete, - + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Update: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -54,6 +61,19 @@ func resourceArmServiceBusNamespaceAuthorizationRuleCreateUpdate(d *schema.Resou resourceGroup := d.Get("resource_group_name").(string) namespaceName := d.Get("namespace_name").(string) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.GetAuthorizationRule(ctx, resourceGroup, namespaceName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Service Bus Namespace Authorization Rule %q (Namespace %q / Resource Group %q): %+v", name, namespaceName, resourceGroup, err) + } + } + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_servicebus_namespace_authorization_rule", *resp.ID) + } + } + parameters := servicebus.SBAuthorizationRule{ Name: &name, SBAuthorizationRuleProperties: &servicebus.SBAuthorizationRuleProperties{ @@ -61,20 +81,21 @@ func resourceArmServiceBusNamespaceAuthorizationRuleCreateUpdate(d *schema.Resou }, } - if _, err := client.CreateOrUpdateAuthorizationRule(ctx, resourceGroup, namespaceName, name, parameters); err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + if _, err := client.CreateOrUpdateAuthorizationRule(waitCtx, resourceGroup, namespaceName, name, parameters); err != nil { return fmt.Errorf("Error creating/updating ServiceBus Namespace Authorization Rule %q (Resource Group %q): %+v", name, resourceGroup, err) } - read, err := client.GetAuthorizationRule(ctx, resourceGroup, namespaceName, name) + resp, err := client.GetAuthorizationRule(ctx, resourceGroup, namespaceName, name) if err != nil { return err } - - if read.ID == nil { + if resp.ID == nil { return fmt.Errorf("Cannot read ServiceBus Namespace Authorization Rule %s (resource group %s) ID", name, resourceGroup) } - d.SetId(*read.ID) + d.SetId(*resp.ID) return resourceArmServiceBusNamespaceAuthorizationRuleRead(d, meta) } @@ -138,7 +159,9 @@ func resourceArmServiceBusNamespaceAuthorizationRuleDelete(d *schema.ResourceDat namespaceName := id.Path["namespaces"] name := id.Path["AuthorizationRules"] //this is slightly different then topic/queue (Authorization vs authorization) - if _, err = client.DeleteAuthorizationRule(ctx, resGroup, namespaceName, name); err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + if _, err = client.DeleteAuthorizationRule(waitCtx, resGroup, namespaceName, name); err != nil { return fmt.Errorf("Error issuing Azure ARM delete request of ServiceBus Namespace Authorization Rule %q (Resource Group %q): %+v", name, resGroup, err) } diff --git a/azurerm/resource_arm_servicebus_namespace_authorization_rule_test.go b/azurerm/resource_arm_servicebus_namespace_authorization_rule_test.go index d3003fe8fd40..275b00aaabd5 100644 --- a/azurerm/resource_arm_servicebus_namespace_authorization_rule_test.go +++ b/azurerm/resource_arm_servicebus_namespace_authorization_rule_test.go @@ -28,6 +28,30 @@ func TestAccAzureRMServiceBusNamespaceAuthorizationRule_manage(t *testing.T) { testAccAzureRMServiceBusNamespaceAuthorizationRule(t, true, true, true) } +func TestAccAzureRMServiceBusNamespaceAuthorizationRule_requiresImport(t *testing.T) { + resourceName := "azurerm_servicebus_namespace_authorization_rule.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMServiceBusNamespaceAuthorizationRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMServiceBusNamespaceAuthorizationRule_base(ri, location, true, true, true), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMServiceBusNamespaceAuthorizationRuleExists(resourceName), + ), + }, + { + Config: testAccAzureRMServiceBusNamespaceAuthorizationRule_requiresImport(ri, location, true, true, true), + ExpectError: testRequiresImportError("azurerm_servicebus_namespace_authorization_rule"), + }, + }, + }) +} + func testAccAzureRMServiceBusNamespaceAuthorizationRule(t *testing.T, listen, send, manage bool) { resourceName := "azurerm_servicebus_namespace_authorization_rule.test" @@ -180,3 +204,19 @@ resource "azurerm_servicebus_namespace_authorization_rule" "test" { } `, rInt, location, listen, send, manage) } + +func testAccAzureRMServiceBusNamespaceAuthorizationRule_requiresImport(rInt int, location string, listen, send, manage bool) string { + template := testAccAzureRMServiceBusNamespaceAuthorizationRule_base(rInt, location, listen, send, manage) + return fmt.Sprintf(` +%s + +resource "azurerm_servicebus_namespace_authorization_rule" "import" { + name = "${azurerm_servicebus_namespace_authorization_rule.test.name}" + namespace_name = "${azurerm_servicebus_namespace_authorization_rule.test.namespace_name}" + resource_group_name = "${azurerm_servicebus_namespace_authorization_rule.test.resource_group_name}" + listen = "${azurerm_servicebus_namespace_authorization_rule.test.listen}" + send = "${azurerm_servicebus_namespace_authorization_rule.test.send}" + manage = "${azurerm_servicebus_namespace_authorization_rule.test.manage}" +} +`, template) +} diff --git a/azurerm/resource_arm_servicebus_namespace_test.go b/azurerm/resource_arm_servicebus_namespace_test.go index 03fc38a5aa41..a6b6b728597e 100644 --- a/azurerm/resource_arm_servicebus_namespace_test.go +++ b/azurerm/resource_arm_servicebus_namespace_test.go @@ -82,6 +82,30 @@ func TestAccAzureRMServiceBusNamespace_basic(t *testing.T) { }) } +func TestAccAzureRMServiceBusNamespace_requiresImport(t *testing.T) { + resourceName := "azurerm_servicebus_namespace.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMServiceBusNamespaceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMServiceBusNamespace_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMServiceBusNamespaceExists(resourceName), + ), + }, + { + Config: testAccAzureRMServiceBusNamespace_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_servicebus_namespace"), + }, + }, + }) +} + func TestAccAzureRMServiceBusNamespace_readDefaultKeys(t *testing.T) { resourceName := "azurerm_servicebus_namespace.test" ri := acctest.RandInt() @@ -215,29 +239,45 @@ func testCheckAzureRMServiceBusNamespaceExists(name string) resource.TestCheckFu func testAccAzureRMServiceBusNamespace_basic(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" + name = "acctestRG-%d" + location = "%s" } + resource "azurerm_servicebus_namespace" "test" { - name = "acctestservicebusnamespace-%d" - location = "${azurerm_resource_group.test.location}" - resource_group_name = "${azurerm_resource_group.test.name}" - sku = "basic" + name = "acctestservicebusnamespace-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "basic" } `, rInt, location, rInt) } +func testAccAzureRMServiceBusNamespace_requiresImport(rInt int, location string) string { + template := testAccAzureRMServiceBusNamespace_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_servicebus_namespace" "import" { + name = "${azurerm_servicebus_namespace.test.name}" + location = "${azurerm_servicebus_namespace.test.location}" + resource_group_name = "${azurerm_servicebus_namespace.test.resource_group_name}" + sku = "${azurerm_servicebus_namespace.test.sku}" +} +`, template) +} + func testAccAzureRMServiceBusNamespaceNonStandardCasing(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" + name = "acctestRG-%d" + location = "%s" } + resource "azurerm_servicebus_namespace" "test" { - name = "acctestservicebusnamespace-%d" - location = "${azurerm_resource_group.test.location}" - resource_group_name = "${azurerm_resource_group.test.name}" - sku = "Basic" + name = "acctestservicebusnamespace-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "Basic" } `, rInt, location, rInt) } diff --git a/azurerm/resource_arm_servicebus_queue.go b/azurerm/resource_arm_servicebus_queue.go index 5d2c3ac96cfb..661ec79ef39b 100644 --- a/azurerm/resource_arm_servicebus_queue.go +++ b/azurerm/resource_arm_servicebus_queue.go @@ -1,12 +1,15 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/servicebus/mgmt/2017-04-01/servicebus" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -16,6 +19,11 @@ func resourceArmServiceBusQueue() *schema.Resource { Read: resourceArmServiceBusQueueRead, Update: resourceArmServiceBusQueueCreateUpdate, Delete: resourceArmServiceBusQueueDelete, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Update: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -130,13 +138,25 @@ func resourceArmServiceBusQueueCreateUpdate(d *schema.ResourceData, meta interfa namespaceName := d.Get("namespace_name").(string) resourceGroup := d.Get("resource_group_name").(string) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, namespaceName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Service Bus Queue %q (Namespace %q / Resource Group %q): %+v", name, namespaceName, resourceGroup, err) + } + } + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_servicebus_queue", *resp.ID) + } + } + enableExpress := d.Get("enable_express").(bool) enablePartitioning := d.Get("enable_partitioning").(bool) maxSize := int32(d.Get("max_size_in_megabytes").(int)) requiresDuplicateDetection := d.Get("requires_duplicate_detection").(bool) requiresSession := d.Get("requires_session").(bool) deadLetteringOnMessageExpiration := d.Get("dead_lettering_on_message_expiration").(bool) - parameters := servicebus.SBQueue{ Name: &name, SBQueueProperties: &servicebus.SBQueueProperties{ @@ -179,8 +199,9 @@ func resourceArmServiceBusQueueCreateUpdate(d *schema.ResourceData, meta interfa return fmt.Errorf("ServiceBus Queue (%s) does not support Express Entities in Premium SKU and must be disabled", name) } - _, err = client.CreateOrUpdate(ctx, resourceGroup, namespaceName, name, parameters) - if err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + if _, err = client.CreateOrUpdate(waitCtx, resourceGroup, namespaceName, name, parameters); err != nil { return err } @@ -271,8 +292,9 @@ func resourceArmServiceBusQueueDelete(d *schema.ResourceData, meta interface{}) namespaceName := id.Path["namespaces"] name := id.Path["queues"] - resp, err := client.Delete(ctx, resourceGroup, namespaceName, name) - if err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + if resp, err := client.Delete(waitCtx, resourceGroup, namespaceName, name); err != nil { if !utils.ResponseWasNotFound(resp) { return err } diff --git a/azurerm/resource_arm_servicebus_queue_authorization_rule.go b/azurerm/resource_arm_servicebus_queue_authorization_rule.go index fd4b93e9aaba..5df095168089 100644 --- a/azurerm/resource_arm_servicebus_queue_authorization_rule.go +++ b/azurerm/resource_arm_servicebus_queue_authorization_rule.go @@ -1,12 +1,15 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/servicebus/mgmt/2017-04-01/servicebus" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -16,7 +19,11 @@ func resourceArmServiceBusQueueAuthorizationRule() *schema.Resource { Read: resourceArmServiceBusQueueAuthorizationRuleRead, Update: resourceArmServiceBusQueueAuthorizationRuleCreateUpdate, Delete: resourceArmServiceBusQueueAuthorizationRuleDelete, - + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Update: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -61,6 +68,19 @@ func resourceArmServiceBusQueueAuthorizationRuleCreateUpdate(d *schema.ResourceD namespaceName := d.Get("namespace_name").(string) queueName := d.Get("queue_name").(string) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.GetAuthorizationRule(ctx, resourceGroup, namespaceName, queueName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Service Bus Queue Authorization Rule %q (Namespace %q / Resource Group %q): %+v", name, namespaceName, resourceGroup, err) + } + } + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_servicebus_namespace_authorization_rule", *resp.ID) + } + } + parameters := servicebus.SBAuthorizationRule{ Name: &name, SBAuthorizationRuleProperties: &servicebus.SBAuthorizationRuleProperties{ @@ -68,20 +88,21 @@ func resourceArmServiceBusQueueAuthorizationRuleCreateUpdate(d *schema.ResourceD }, } - if _, err := client.CreateOrUpdateAuthorizationRule(ctx, resourceGroup, namespaceName, queueName, name, parameters); err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + if _, err := client.CreateOrUpdateAuthorizationRule(waitCtx, resourceGroup, namespaceName, queueName, name, parameters); err != nil { return fmt.Errorf("Error creating/updating ServiceBus Queue Authorization Rule %q (Resource Group %q): %+v", name, resourceGroup, err) } - read, err := client.GetAuthorizationRule(ctx, resourceGroup, namespaceName, queueName, name) + resp, err := client.GetAuthorizationRule(ctx, resourceGroup, namespaceName, queueName, name) if err != nil { return err } - - if read.ID == nil { + if resp.ID == nil { return fmt.Errorf("Cannot read ServiceBus Namespace Queue Authorization Rule %q (Queue %q / Namespace %q / Resource Group %q) ID", name, queueName, namespaceName, resourceGroup) } - d.SetId(*read.ID) + d.SetId(*resp.ID) return resourceArmServiceBusQueueAuthorizationRuleRead(d, meta) } @@ -149,7 +170,9 @@ func resourceArmServiceBusQueueAuthorizationRuleDelete(d *schema.ResourceData, m name := id.Path["authorizationRules"] queueName := id.Path["queues"] - if _, err = client.DeleteAuthorizationRule(ctx, resGroup, namespaceName, queueName, name); err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + if _, err = client.DeleteAuthorizationRule(waitCtx, resGroup, namespaceName, queueName, name); err != nil { return fmt.Errorf("Error issuing delete request of ServiceBus Queue Authorization Rule %q (Queue %q / Namespace %q / Resource Group %q): %+v", name, queueName, namespaceName, resGroup, err) } diff --git a/azurerm/resource_arm_servicebus_queue_authorization_rule_test.go b/azurerm/resource_arm_servicebus_queue_authorization_rule_test.go index 681816f99591..cf7ae5a64179 100644 --- a/azurerm/resource_arm_servicebus_queue_authorization_rule_test.go +++ b/azurerm/resource_arm_servicebus_queue_authorization_rule_test.go @@ -27,6 +27,30 @@ func TestAccAzureRMServiceBusQueueAuthorizationRule_manage(t *testing.T) { testAccAzureRMServiceBusQueueAuthorizationRule(t, true, true, true) } +func TestAccAzureRMServiceBusQueueAuthorizationRule_requiresImport(t *testing.T) { + resourceName := "azurerm_servicebus_queue_authorization_rule.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMServiceBusQueueAuthorizationRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMServiceBusQueueAuthorizationRule_base(ri, location, true, true, true), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMServiceBusQueueAuthorizationRuleExists(resourceName), + ), + }, + { + Config: testAccAzureRMServiceBusQueueAuthorizationRule_requiresImport(ri, location, true, true, true), + ExpectError: testRequiresImportError("azurerm_servicebus_queue_authorization_rule"), + }, + }, + }) +} + func testAccAzureRMServiceBusQueueAuthorizationRule(t *testing.T, listen, send, manage bool) { resourceName := "azurerm_servicebus_queue_authorization_rule.test" @@ -192,3 +216,20 @@ resource "azurerm_servicebus_queue_authorization_rule" "test" { } `, rInt, location, listen, send, manage) } + +func testAccAzureRMServiceBusQueueAuthorizationRule_requiresImport(rInt int, location string, listen, send, manage bool) string { + template := testAccAzureRMServiceBusQueueAuthorizationRule_base(rInt, location, listen, send, manage) + return fmt.Sprintf(` +%s + +resource "azurerm_servicebus_queue_authorization_rule" "import" { + name = "${azurerm_servicebus_queue_authorization_rule.test.name}" + namespace_name = "${azurerm_servicebus_queue_authorization_rule.test.namespace_name}" + queue_name = "${azurerm_servicebus_queue_authorization_rule.test.queue_name}" + resource_group_name = "${azurerm_servicebus_queue_authorization_rule.test.resource_group_name}" + listen = "${azurerm_servicebus_queue_authorization_rule.test.listen}" + send = "${azurerm_servicebus_queue_authorization_rule.test.send}" + manage = "${azurerm_servicebus_queue_authorization_rule.test.manage}" +} +`, template) +} diff --git a/azurerm/resource_arm_servicebus_queue_test.go b/azurerm/resource_arm_servicebus_queue_test.go index b50fdeadaabe..6302618609ae 100644 --- a/azurerm/resource_arm_servicebus_queue_test.go +++ b/azurerm/resource_arm_servicebus_queue_test.go @@ -33,6 +33,30 @@ func TestAccAzureRMServiceBusQueue_basic(t *testing.T) { }) } +func TestAccAzureRMServiceBusQueue_requiresImport(t *testing.T) { + resourceName := "azurerm_servicebus_queue.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMServiceBusQueueDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMServiceBusQueue_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMServiceBusQueueExists(resourceName), + ), + }, + { + Config: testAccAzureRMServiceBusQueue_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_servicebus_queue"), + }, + }, + }) +} + func TestAccAzureRMServiceBusQueue_update(t *testing.T) { resourceName := "azurerm_servicebus_queue.test" ri := acctest.RandInt() @@ -336,6 +360,19 @@ resource "azurerm_servicebus_queue" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMServiceBusQueue_requiresImport(rInt int, location string) string { + template := testAccAzureRMServiceBusQueue_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_servicebus_queue" "import" { + name = "${azurerm_servicebus_queue.test.name}" + resource_group_name = "${azurerm_servicebus_queue.test.resource_group_name}" + namespace_name = "${azurerm_servicebus_queue.test.namespace_name}" +} +`, template) +} + func testAccAzureRMServiceBusQueue_Premium(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_servicebus_subscription.go b/azurerm/resource_arm_servicebus_subscription.go index 946ff34def52..ae156974c4af 100644 --- a/azurerm/resource_arm_servicebus_subscription.go +++ b/azurerm/resource_arm_servicebus_subscription.go @@ -1,21 +1,30 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/servicebus/mgmt/2017-04-01/servicebus" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) func resourceArmServiceBusSubscription() *schema.Resource { return &schema.Resource{ - Create: resourceArmServiceBusSubscriptionCreate, + Create: resourceArmServiceBusSubscriptionCreateUpdate, Read: resourceArmServiceBusSubscriptionRead, - Update: resourceArmServiceBusSubscriptionCreate, + Update: resourceArmServiceBusSubscriptionCreateUpdate, Delete: resourceArmServiceBusSubscriptionDelete, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Update: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -101,7 +110,7 @@ func resourceArmServiceBusSubscription() *schema.Resource { } } -func resourceArmServiceBusSubscriptionCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmServiceBusSubscriptionCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).serviceBusSubscriptionsClient ctx := meta.(*ArmClient).StopContext log.Printf("[INFO] preparing arguments for Azure ARM ServiceBus Subscription creation.") @@ -111,11 +120,23 @@ func resourceArmServiceBusSubscriptionCreate(d *schema.ResourceData, meta interf namespaceName := d.Get("namespace_name").(string) resourceGroup := d.Get("resource_group_name").(string) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, namespaceName, topicName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Service Bus Subscription %q (Topic %q / Namespace %q / Resource Group %q): %+v", name, topicName, namespaceName, resourceGroup, err) + } + } + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_servicebus_subscription", *resp.ID) + } + } + deadLetteringExpiration := d.Get("dead_lettering_on_message_expiration").(bool) enableBatchedOps := d.Get("enable_batched_operations").(bool) maxDeliveryCount := int32(d.Get("max_delivery_count").(int)) requiresSession := d.Get("requires_session").(bool) - parameters := servicebus.SBSubscription{ SBSubscriptionProperties: &servicebus.SBSubscriptionProperties{ DeadLetteringOnMessageExpiration: &deadLetteringExpiration, @@ -141,8 +162,9 @@ func resourceArmServiceBusSubscriptionCreate(d *schema.ResourceData, meta interf parameters.DefaultMessageTimeToLive = &defaultMessageTtl } - _, err := client.CreateOrUpdate(ctx, resourceGroup, namespaceName, topicName, name, parameters) - if err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + if _, err := client.CreateOrUpdate(waitCtx, resourceGroup, namespaceName, topicName, name, parameters); err != nil { return err } @@ -216,7 +238,13 @@ func resourceArmServiceBusSubscriptionDelete(d *schema.ResourceData, meta interf topicName := id.Path["topics"] name := id.Path["subscriptions"] - _, err = client.Delete(ctx, resourceGroup, namespaceName, topicName, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + if resp, err := client.Delete(waitCtx, resourceGroup, namespaceName, topicName, name); err != nil { + if !response.WasNotFound(resp.Response) { + return fmt.Errorf("Error deleting ServiceBus Subscription %q: %+v", name, err) + } + } return err } diff --git a/azurerm/resource_arm_servicebus_subscription_rule.go b/azurerm/resource_arm_servicebus_subscription_rule.go index fb413e1d8a37..7d5403fbf305 100644 --- a/azurerm/resource_arm_servicebus_subscription_rule.go +++ b/azurerm/resource_arm_servicebus_subscription_rule.go @@ -1,14 +1,17 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/servicebus/mgmt/2017-04-01/servicebus" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -18,6 +21,11 @@ func resourceArmServiceBusSubscriptionRule() *schema.Resource { Read: resourceArmServiceBusSubscriptionRuleRead, Update: resourceArmServiceBusSubscriptionRuleCreateUpdate, Delete: resourceArmServiceBusSubscriptionRuleDelete, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Update: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -130,6 +138,20 @@ func resourceArmServiceBusSubscriptionRuleCreateUpdate(d *schema.ResourceData, m namespaceName := d.Get("namespace_name").(string) resourceGroup := d.Get("resource_group_name").(string) filterType := d.Get("filter_type").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, namespaceName, topicName, subscriptionName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Service Bus Subscription Rule %q (Topic %q / Namespace %q / Resource Group %q): %+v", name, topicName, namespaceName, resourceGroup, err) + } + } + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_servicebus_subscription_rule", *resp.ID) + } + } + rule := servicebus.Rule{ Ruleproperties: &servicebus.Ruleproperties{ FilterType: servicebus.FilterType(filterType), @@ -158,20 +180,21 @@ func resourceArmServiceBusSubscriptionRuleCreateUpdate(d *schema.ResourceData, m } } - _, err := client.CreateOrUpdate(ctx, resourceGroup, namespaceName, topicName, subscriptionName, name, rule) - if err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + if _, err := client.CreateOrUpdate(waitCtx, resourceGroup, namespaceName, topicName, subscriptionName, name, rule); err != nil { return err } - read, err := client.Get(ctx, resourceGroup, namespaceName, topicName, subscriptionName, name) + resp, err := client.Get(ctx, resourceGroup, namespaceName, topicName, subscriptionName, name) if err != nil { return err } - if read.ID == nil { + if resp.ID == nil { return fmt.Errorf("Cannot read Service Bus Subscription Rule %s (resource group %s) ID", name, resourceGroup) } - d.SetId(*read.ID) + d.SetId(*resp.ID) return resourceArmServiceBusSubscriptionRuleRead(d, meta) } @@ -239,8 +262,9 @@ func resourceArmServiceBusSubscriptionRuleDelete(d *schema.ResourceData, meta in subscriptionName := id.Path["subscriptions"] name := id.Path["rules"] - resp, err := client.Delete(ctx, resourceGroup, namespaceName, topicName, subscriptionName, name) - if err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + if resp, err := client.Delete(waitCtx, resourceGroup, namespaceName, topicName, subscriptionName, name); err != nil { if !response.WasNotFound(resp.Response) { return fmt.Errorf("Error deleting ServiceBus Subscription Rule %q: %+v", name, err) } diff --git a/azurerm/resource_arm_servicebus_subscription_rule_test.go b/azurerm/resource_arm_servicebus_subscription_rule_test.go index e2cde6556602..71e6c807a124 100644 --- a/azurerm/resource_arm_servicebus_subscription_rule_test.go +++ b/azurerm/resource_arm_servicebus_subscription_rule_test.go @@ -30,6 +30,30 @@ func TestAccAzureRMServiceBusSubscriptionRule_basicSqlFilter(t *testing.T) { }) } +func TestAccAzureRMServiceBusSubscriptionRule_requiresImport(t *testing.T) { + resourceName := "azurerm_servicebus_subscription_rule.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMServiceBusTopicDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMServiceBusSubscriptionRule_basicSqlFilter(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMServiceBusSubscriptionRuleExists(resourceName), + ), + }, + { + Config: testAccAzureRMServiceBusSubscriptionRule_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_servicebus_subscription_rule"), + }, + }, + }) +} + func TestAccAzureRMServiceBusSubscriptionRule_basicCorrelationFilter(t *testing.T) { resourceName := "azurerm_servicebus_subscription_rule.test" ri := acctest.RandInt() @@ -223,12 +247,29 @@ resource "azurerm_servicebus_subscription_rule" "test" { topic_name = "${azurerm_servicebus_topic.test.name}" subscription_name = "${azurerm_servicebus_subscription.test.name}" resource_group_name = "${azurerm_resource_group.test.name}" - filter_type = "SqlFilter" - sql_filter = "2=2" + filter_type = "SqlFilter" + sql_filter = "2=2" } `, template, rInt) } +func testAccAzureRMServiceBusSubscriptionRule_requiresImport(rInt int, location string) string { + template := testAccAzureRMServiceBusSubscriptionRule_basicSqlFilter(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_servicebus_subscription_rule" "import" { + name = "${azurerm_servicebus_subscription_rule.test.name}" + namespace_name = "${azurerm_servicebus_subscription_rule.test.namespace_name}" + topic_name = "${azurerm_servicebus_subscription_rule.test.topic_name}" + subscription_name = "${azurerm_servicebus_subscription_rule.test.subscription_name}" + resource_group_name = "${azurerm_servicebus_subscription_rule.test.resource_group_name}" + filter_type = "${azurerm_servicebus_subscription_rule.test.filter_type}" + sql_filter = "${azurerm_servicebus_subscription_rule.test.sql_filter}" +} +`, template) +} + func testAccAzureRMServiceBusSubscriptionRule_basicSqlFilterUpdated(rInt int, location string) string { template := testAccAzureRMServiceBusSubscriptionRule_template(rInt, location) return fmt.Sprintf(` @@ -240,8 +281,8 @@ resource "azurerm_servicebus_subscription_rule" "test" { topic_name = "${azurerm_servicebus_topic.test.name}" subscription_name = "${azurerm_servicebus_subscription.test.name}" resource_group_name = "${azurerm_resource_group.test.name}" - filter_type = "SqlFilter" - sql_filter = "3=3" + filter_type = "SqlFilter" + sql_filter = "3=3" } `, template, rInt) } @@ -257,9 +298,9 @@ resource "azurerm_servicebus_subscription_rule" "test" { topic_name = "${azurerm_servicebus_topic.test.name}" subscription_name = "${azurerm_servicebus_subscription.test.name}" resource_group_name = "${azurerm_resource_group.test.name}" - filter_type = "SqlFilter" - sql_filter = "2=2" - action = "SET Test='true'" + filter_type = "SqlFilter" + sql_filter = "2=2" + action = "SET Test='true'" } `, template, rInt) } @@ -275,9 +316,9 @@ resource "azurerm_servicebus_subscription_rule" "test" { topic_name = "${azurerm_servicebus_topic.test.name}" subscription_name = "${azurerm_servicebus_subscription.test.name}" resource_group_name = "${azurerm_resource_group.test.name}" - filter_type = "CorrelationFilter" + filter_type = "CorrelationFilter" - correlation_filter = { + correlation_filter { correlation_id = "test_correlation_id" message_id = "test_message_id" to = "test_to" @@ -304,7 +345,7 @@ resource "azurerm_servicebus_subscription_rule" "test" { resource_group_name = "${azurerm_resource_group.test.name}" filter_type = "CorrelationFilter" - correlation_filter = { + correlation_filter { correlation_id = "test_correlation_id" message_id = "test_message_id" } @@ -325,7 +366,7 @@ resource "azurerm_servicebus_subscription_rule" "test" { resource_group_name = "${azurerm_resource_group.test.name}" filter_type = "CorrelationFilter" - correlation_filter = { + correlation_filter { correlation_id = "test_correlation_id" message_id = "test_message_id_updated" reply_to = "test_reply_to_added" @@ -348,7 +389,7 @@ resource "azurerm_servicebus_subscription_rule" "test" { action = "SET Test='true'" filter_type = "CorrelationFilter" - correlation_filter = { + correlation_filter { correlation_id = "test_correlation_id" message_id = "test_message_id" } diff --git a/azurerm/resource_arm_servicebus_subscription_test.go b/azurerm/resource_arm_servicebus_subscription_test.go index bc007856083a..38671f202ddd 100644 --- a/azurerm/resource_arm_servicebus_subscription_test.go +++ b/azurerm/resource_arm_servicebus_subscription_test.go @@ -30,6 +30,29 @@ func TestAccAzureRMServiceBusSubscription_basic(t *testing.T) { }) } +func TestAccAzureRMServiceBusSubscription_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMServiceBusTopicDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMServiceBusSubscription_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMServiceBusSubscriptionExists("azurerm_servicebus_subscription.test"), + ), + }, + { + Config: testAccAzureRMServiceBusSubscription_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_servicebus_subscription"), + }, + }, + }) +} + func TestAccAzureRMServiceBusSubscription_defaultTtl(t *testing.T) { ri := acctest.RandInt() config := testAccAzureRMServiceBusSubscription_withDefaultTtl(ri, testLocation()) @@ -198,64 +221,178 @@ func testCheckAzureRMServiceBusSubscriptionExists(name string) resource.TestChec } } -const testAccAzureRMServiceBusSubscription_tfTemplate = ` +func testAccAzureRMServiceBusSubscription_basic(rInt int, location string) string { + return fmt.Sprintf(` resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" + name = "acctestRG-%d" + location = "%s" } resource "azurerm_servicebus_namespace" "test" { - name = "acctestservicebusnamespace-%d" - location = "${azurerm_resource_group.test.location}" - resource_group_name = "${azurerm_resource_group.test.name}" - sku = "standard" + name = "acctestservicebusnamespace-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "standard" } resource "azurerm_servicebus_topic" "test" { - name = "acctestservicebustopic-%d" - namespace_name = "${azurerm_servicebus_namespace.test.name}" - resource_group_name = "${azurerm_resource_group.test.name}" + name = "acctestservicebustopic-%d" + namespace_name = "${azurerm_servicebus_namespace.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" } resource "azurerm_servicebus_subscription" "test" { - name = "acctestservicebussubscription-%d" - namespace_name = "${azurerm_servicebus_namespace.test.name}" - topic_name = "${azurerm_servicebus_topic.test.name}" - resource_group_name = "${azurerm_resource_group.test.name}" - max_delivery_count = 10 - %s + name = "acctestservicebussubscription-%d" + namespace_name = "${azurerm_servicebus_namespace.test.name}" + topic_name = "${azurerm_servicebus_topic.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + max_delivery_count = 10 +} +`, rInt, location, rInt, rInt, rInt) } -` -func testAccAzureRMServiceBusSubscription_basic(rInt int, location string) string { - return fmt.Sprintf(testAccAzureRMServiceBusSubscription_tfTemplate, rInt, location, rInt, rInt, rInt, "") +func testAccAzureRMServiceBusSubscription_requiresImport(rInt int, location string) string { + template := testAccAzureRMServiceBusSubscription_basic(rInt, location) + return fmt.Sprintf( + ` +%s + +resource "azurerm_servicebus_subscription" "import" { + name = "${azurerm_servicebus_subscription.test.name}" + namespace_name = "${azurerm_servicebus_subscription.test.namespace_name}" + topic_name = "${azurerm_servicebus_subscription.test.topic_name}" + resource_group_name = "${azurerm_servicebus_subscription.test.resource_group_name}" + max_delivery_count = "${azurerm_servicebus_subscription.test.max_delivery_count}" +} +`, template) } func testAccAzureRMServiceBusSubscription_withDefaultTtl(rInt int, location string) string { - return fmt.Sprintf(testAccAzureRMServiceBusSubscription_tfTemplate, rInt, location, rInt, rInt, rInt, - "default_message_ttl = \"PT1H\"\n") + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_servicebus_namespace" "test" { + name = "acctestservicebusnamespace-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "standard" +} + +resource "azurerm_servicebus_topic" "test" { + name = "acctestservicebustopic-%d" + namespace_name = "${azurerm_servicebus_namespace.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_servicebus_subscription" "test" { + name = "acctestservicebussubscription-%d" + namespace_name = "${azurerm_servicebus_namespace.test.name}" + topic_name = "${azurerm_servicebus_topic.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + max_delivery_count = 10 + default_message_ttl = "PT1H" +} +`, rInt, location, rInt, rInt, rInt) } func testAccAzureRMServiceBusSubscription_updateEnableBatched(rInt int, location string) string { - return fmt.Sprintf(testAccAzureRMServiceBusSubscription_tfTemplate, rInt, location, rInt, rInt, rInt, - "enable_batched_operations = true\n") + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_servicebus_namespace" "test" { + name = "acctestservicebusnamespace-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "standard" +} + +resource "azurerm_servicebus_topic" "test" { + name = "acctestservicebustopic-%d" + namespace_name = "${azurerm_servicebus_namespace.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_servicebus_subscription" "test" { + name = "acctestservicebussubscription-%d" + namespace_name = "${azurerm_servicebus_namespace.test.name}" + topic_name = "${azurerm_servicebus_topic.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + max_delivery_count = 10 + enable_batched_operations = true +} +`, rInt, location, rInt, rInt, rInt) } func testAccAzureRMServiceBusSubscription_updateRequiresSession(rInt int, location string) string { - return fmt.Sprintf(testAccAzureRMServiceBusSubscription_tfTemplate, rInt, location, rInt, rInt, rInt, - "requires_session = true\n") + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_servicebus_namespace" "test" { + name = "acctestservicebusnamespace-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "standard" +} + +resource "azurerm_servicebus_topic" "test" { + name = "acctestservicebustopic-%d" + namespace_name = "${azurerm_servicebus_namespace.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_servicebus_subscription" "test" { + name = "acctestservicebussubscription-%d" + namespace_name = "${azurerm_servicebus_namespace.test.name}" + topic_name = "${azurerm_servicebus_topic.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + max_delivery_count = 10 + requires_session = true +} +`, rInt, location, rInt, rInt, rInt) } func testAccAzureRMServiceBusSubscription_updateForwardTo(rInt int, location string) string { - forwardToTf := testAccAzureRMServiceBusSubscription_tfTemplate + ` + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_servicebus_namespace" "test" { + name = "acctestservicebusnamespace-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "standard" +} + +resource "azurerm_servicebus_topic" "test" { + name = "acctestservicebustopic-%d" + namespace_name = "${azurerm_servicebus_namespace.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" +} resource "azurerm_servicebus_topic" "forward_to" { - name = "acctestservicebustopic-forward_to-%d" - namespace_name = "${azurerm_servicebus_namespace.test.name}" - resource_group_name = "${azurerm_resource_group.test.name}" + name = "acctestservicebustopic-forward_to-%d" + namespace_name = "${azurerm_servicebus_namespace.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" } -` - return fmt.Sprintf(forwardToTf, rInt, location, rInt, rInt, rInt, - "forward_to = \"${azurerm_servicebus_topic.forward_to.name}\"\n", rInt) +resource "azurerm_servicebus_subscription" "test" { + name = "acctestservicebussubscription-%d" + namespace_name = "${azurerm_servicebus_namespace.test.name}" + topic_name = "${azurerm_servicebus_topic.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + max_delivery_count = 10 + forward_to = "${azurerm_servicebus_topic.forward_to.name}" +} +`, rInt, location, rInt, rInt, rInt, rInt) } diff --git a/azurerm/resource_arm_servicebus_topic.go b/azurerm/resource_arm_servicebus_topic.go index 64d5dd85fe84..6cf687a28b27 100644 --- a/azurerm/resource_arm_servicebus_topic.go +++ b/azurerm/resource_arm_servicebus_topic.go @@ -1,23 +1,30 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/servicebus/mgmt/2017-04-01/servicebus" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) func resourceArmServiceBusTopic() *schema.Resource { return &schema.Resource{ - Create: resourceArmServiceBusTopicCreate, + Create: resourceArmServiceBusTopicCreateUpdate, Read: resourceArmServiceBusTopicRead, - Update: resourceArmServiceBusTopicCreate, + Update: resourceArmServiceBusTopicCreateUpdate, Delete: resourceArmServiceBusTopicDelete, - + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Update: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -116,7 +123,7 @@ func resourceArmServiceBusTopic() *schema.Resource { } } -func resourceArmServiceBusTopicCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmServiceBusTopicCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).serviceBusTopicsClient ctx := meta.(*ArmClient).StopContext log.Printf("[INFO] preparing arguments for Azure ServiceBus Topic creation.") @@ -124,15 +131,28 @@ func resourceArmServiceBusTopicCreate(d *schema.ResourceData, meta interface{}) name := d.Get("name").(string) namespaceName := d.Get("namespace_name").(string) resourceGroup := d.Get("resource_group_name").(string) - status := d.Get("status").(string) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, namespaceName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Service Bus Topic %q (Namespace %q / Resource Group %q): %+v", name, namespaceName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_servicebus_topic", *resp.ID) + } + } + + status := d.Get("status").(string) enableBatchedOps := d.Get("enable_batched_operations").(bool) enableExpress := d.Get("enable_express").(bool) enablePartitioning := d.Get("enable_partitioning").(bool) maxSize := int32(d.Get("max_size_in_megabytes").(int)) requiresDuplicateDetection := d.Get("requires_duplicate_detection").(bool) supportOrdering := d.Get("support_ordering").(bool) - parameters := servicebus.SBTopic{ Name: &name, SBTopicProperties: &servicebus.SBTopicProperties{ @@ -158,20 +178,21 @@ func resourceArmServiceBusTopicCreate(d *schema.ResourceData, meta interface{}) parameters.SBTopicProperties.DuplicateDetectionHistoryTimeWindow = utils.String(duplicateWindow) } - _, err := client.CreateOrUpdate(ctx, resourceGroup, namespaceName, name, parameters) - if err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + if _, err := client.CreateOrUpdate(waitCtx, resourceGroup, namespaceName, name, parameters); err != nil { return err } - read, err := client.Get(ctx, resourceGroup, namespaceName, name) + resp, err := client.Get(ctx, resourceGroup, namespaceName, name) if err != nil { return err } - if read.ID == nil { + if resp.ID == nil { return fmt.Errorf("Cannot read ServiceBus Topic %s (resource group %s) ID", name, resourceGroup) } - d.SetId(*read.ID) + d.SetId(*resp.ID) return resourceArmServiceBusTopicRead(d, meta) } @@ -253,8 +274,10 @@ func resourceArmServiceBusTopicDelete(d *schema.ResourceData, meta interface{}) namespaceName := id.Path["namespaces"] name := id.Path["topics"] - resp, err := client.Delete(ctx, resourceGroup, namespaceName, name) - if err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + + if resp, err := client.Delete(waitCtx, resourceGroup, namespaceName, name); err != nil { if !utils.ResponseWasNotFound(resp) { return err } diff --git a/azurerm/resource_arm_servicebus_topic_authorization_rule.go b/azurerm/resource_arm_servicebus_topic_authorization_rule.go index e20dae5c325a..7657e4077378 100644 --- a/azurerm/resource_arm_servicebus_topic_authorization_rule.go +++ b/azurerm/resource_arm_servicebus_topic_authorization_rule.go @@ -1,13 +1,15 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/servicebus/mgmt/2017-04-01/servicebus" - "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -17,7 +19,11 @@ func resourceArmServiceBusTopicAuthorizationRule() *schema.Resource { Read: resourceArmServiceBusTopicAuthorizationRuleRead, Update: resourceArmServiceBusTopicAuthorizationRuleCreateUpdate, Delete: resourceArmServiceBusTopicAuthorizationRuleDelete, - + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Update: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -61,6 +67,19 @@ func resourceArmServiceBusTopicAuthorizationRuleCreateUpdate(d *schema.ResourceD topicName := d.Get("topic_name").(string) resourceGroup := d.Get("resource_group_name").(string) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.GetAuthorizationRule(ctx, resourceGroup, namespaceName, topicName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Service Bus Topic Rule %q (Namespace %q / Resource Group %q): %+v", name, namespaceName, resourceGroup, err) + } + } + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_servicebus_subscription_rule", *resp.ID) + } + } + parameters := servicebus.SBAuthorizationRule{ Name: &name, SBAuthorizationRuleProperties: &servicebus.SBAuthorizationRuleProperties{ @@ -68,20 +87,21 @@ func resourceArmServiceBusTopicAuthorizationRuleCreateUpdate(d *schema.ResourceD }, } - if _, err := client.CreateOrUpdateAuthorizationRule(ctx, resourceGroup, namespaceName, topicName, name, parameters); err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + if _, err := client.CreateOrUpdateAuthorizationRule(waitCtx, resourceGroup, namespaceName, topicName, name, parameters); err != nil { return fmt.Errorf("Error creating/updating ServiceBus Topic Authorization Rule %q (Resource Group %q): %+v", name, resourceGroup, err) } - read, err := client.GetAuthorizationRule(ctx, resourceGroup, namespaceName, topicName, name) + resp, err := client.GetAuthorizationRule(ctx, resourceGroup, namespaceName, topicName, name) if err != nil { return err } - - if read.ID == nil { + if resp.ID == nil { return fmt.Errorf("Cannot read ServiceBus Topic Authorization Rule %s (resource group %s) ID", name, resourceGroup) } - d.SetId(*read.ID) + d.SetId(*resp.ID) return resourceArmServiceBusTopicAuthorizationRuleRead(d, meta) } @@ -148,7 +168,9 @@ func resourceArmServiceBusTopicAuthorizationRuleDelete(d *schema.ResourceData, m topicName := id.Path["topics"] name := id.Path["authorizationRules"] - if _, err = client.DeleteAuthorizationRule(ctx, resGroup, namespaceName, topicName, name); err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + if _, err = client.DeleteAuthorizationRule(waitCtx, resGroup, namespaceName, topicName, name); err != nil { return fmt.Errorf("Error issuing Azure ARM delete request of ServiceBus Topic Authorization Rule %q (Resource Group %q): %+v", name, resGroup, err) } diff --git a/azurerm/resource_arm_servicebus_topic_authorization_rule_test.go b/azurerm/resource_arm_servicebus_topic_authorization_rule_test.go index 013b2b4870c1..976c7ccc4386 100644 --- a/azurerm/resource_arm_servicebus_topic_authorization_rule_test.go +++ b/azurerm/resource_arm_servicebus_topic_authorization_rule_test.go @@ -27,6 +27,30 @@ func TestAccAzureRMServiceBusTopicAuthorizationRule_manage(t *testing.T) { testAccAzureRMServiceBusTopicAuthorizationRule(t, true, true, true) } +func TestAccAzureRMServiceBusTopicAuthorizationRule_requiresImport(t *testing.T) { + resourceName := "azurerm_servicebus_topic_authorization_rule.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMServiceBusTopicAuthorizationRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMServiceBusTopicAuthorizationRule_base(ri, location, true, true, true), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMServiceBusTopicAuthorizationRuleExists(resourceName), + ), + }, + { + Config: testAccAzureRMServiceBusTopicAuthorizationRule_requiresImport(ri, location, true, true, true), + ExpectError: testRequiresImportError("azurerm_servicebus_topic_authorization_rule"), + }, + }, + }) +} + func testAccAzureRMServiceBusTopicAuthorizationRule(t *testing.T, listen, send, manage bool) { resourceName := "azurerm_servicebus_topic_authorization_rule.test" @@ -188,3 +212,20 @@ resource "azurerm_servicebus_topic_authorization_rule" "test" { } `, rInt, location, listen, send, manage) } + +func testAccAzureRMServiceBusTopicAuthorizationRule_requiresImport(rInt int, location string, listen, send, manage bool) string { + template := testAccAzureRMServiceBusTopicAuthorizationRule_base(rInt, location, listen, send, manage) + return fmt.Sprintf(` +%s + +resource "azurerm_servicebus_topic_authorization_rule" "import" { + name = "${azurerm_servicebus_topic_authorization_rule.test.name}" + namespace_name = "${azurerm_servicebus_topic_authorization_rule.test.namespace_name}" + resource_group_name = "${azurerm_servicebus_topic_authorization_rule.test.resource_group_name}" + topic_name = "${azurerm_servicebus_topic_authorization_rule.test.topic_name}" + listen = "${azurerm_servicebus_topic_authorization_rule.test.listen}" + send = "${azurerm_servicebus_topic_authorization_rule.test.send}" + manage = "${azurerm_servicebus_topic_authorization_rule.test.manage}" +} +`, template) +} diff --git a/azurerm/resource_arm_servicebus_topic_test.go b/azurerm/resource_arm_servicebus_topic_test.go index 8842af0c7082..5874190cb9e5 100644 --- a/azurerm/resource_arm_servicebus_topic_test.go +++ b/azurerm/resource_arm_servicebus_topic_test.go @@ -31,6 +31,30 @@ func TestAccAzureRMServiceBusTopic_basic(t *testing.T) { }) } +func TestAccAzureRMServiceBusTopic_requiresImport(t *testing.T) { + resourceName := "azurerm_servicebus_topic.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMServiceBusTopicDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMServiceBusTopic_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMServiceBusTopicExists(resourceName), + ), + }, + { + Config: testAccAzureRMServiceBusTopic_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_servicebus_topic"), + }, + }, + }) +} + func TestAccAzureRMServiceBusTopic_basicDisabled(t *testing.T) { resourceName := "azurerm_servicebus_topic.test" ri := acctest.RandInt() @@ -307,6 +331,19 @@ resource "azurerm_servicebus_topic" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMServiceBusTopic_requiresImport(rInt int, location string) string { + template := testAccAzureRMServiceBusTopic_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_servicebus_topic" "import" { + name = "${azurerm_servicebus_topic.test.name}" + namespace_name = "${azurerm_servicebus_topic.test.namespace_name}" + resource_group_name = "${azurerm_servicebus_topic.test.resource_group_name}" +} +`, template) +} + func testAccAzureRMServiceBusTopic_basicDisabled(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_snapshot.go b/azurerm/resource_arm_snapshot.go index 5e1574ef4ff7..da24469954a5 100644 --- a/azurerm/resource_arm_snapshot.go +++ b/azurerm/resource_arm_snapshot.go @@ -1,13 +1,16 @@ package azurerm import ( + "context" "fmt" "log" "regexp" + "time" "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-06-01/compute" "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 resourceArmSnapshot() *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": { @@ -80,6 +88,21 @@ func resourceArmSnapshotCreateUpdate(d *schema.ResourceData, meta interface{}) e name := d.Get("name").(string) resourceGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Snapshot %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_snapshot", *resp.ID) + } + } + location := azureRMNormalizeLocation(d.Get("location").(string)) createOption := d.Get("create_option").(string) tags := d.Get("tags").(map[string]interface{}) @@ -122,7 +145,9 @@ func resourceArmSnapshotCreateUpdate(d *schema.ResourceData, meta interface{}) e return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -207,7 +232,9 @@ func resourceArmSnapshotDelete(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error deleting Snapshot: %+v", err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error deleting Snapshot: %+v", err) } diff --git a/azurerm/resource_arm_snapshot_test.go b/azurerm/resource_arm_snapshot_test.go index a25ad3326faa..09d2237fda80 100644 --- a/azurerm/resource_arm_snapshot_test.go +++ b/azurerm/resource_arm_snapshot_test.go @@ -79,6 +79,30 @@ func TestAccAzureRMSnapshot_fromManagedDisk(t *testing.T) { }) } +func TestAccAzureRMSnapshot_requiresImport(t *testing.T) { + resourceName := "azurerm_snapshot.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSnapshotDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSnapshot_fromManagedDisk(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSnapshotExists(resourceName), + ), + }, + { + Config: testAccAzureRMSnapshot_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_snapshot"), + }, + }, + }) +} + func TestAccAzureRMSnapshot_encryption(t *testing.T) { resourceName := "azurerm_snapshot.test" ri := acctest.RandInt() @@ -269,6 +293,21 @@ resource "azurerm_snapshot" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMSnapshot_requiresImport(rInt int, location string) string { + template := testAccAzureRMSnapshot_fromManagedDisk(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_snapshot" "import" { + name = "${azurerm_snapshot.test.name}" + location = "${azurerm_snapshot.test.location}" + resource_group_name = "${azurerm_snapshot.test.resource_group_name}" + create_option = "${azurerm_snapshot.test.create_option}" + source_uri = "${azurerm_snapshot.test.source_uri}" +} +`, template) +} + func testAccAzureRMSnapshot_fromManagedDiskUpdated(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_sql_administrator.go b/azurerm/resource_arm_sql_administrator.go index 24c0d8ac5f47..7e1fd80b07df 100644 --- a/azurerm/resource_arm_sql_administrator.go +++ b/azurerm/resource_arm_sql_administrator.go @@ -1,12 +1,15 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/2015-05-01-preview/sql" "github.com/hashicorp/terraform/helper/schema" uuid "github.com/satori/go.uuid" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -19,6 +22,11 @@ func resourceArmSqlAdministrator() *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{ "server_name": { @@ -55,6 +63,21 @@ func resourceArmSqlActiveDirectoryAdministratorCreateUpdate(d *schema.ResourceDa serverName := d.Get("server_name").(string) resGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, serverName) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of SQL AD Administrator %q (Resource Group %q): %+v", serverName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_sql_active_directory_administrator", *resp.ID) + } + } + login := d.Get("login").(string) objectId := uuid.FromStringOrNil(d.Get("object_id").(string)) tenantId := uuid.FromStringOrNil(d.Get("tenant_id").(string)) @@ -72,7 +95,9 @@ func resourceArmSqlActiveDirectoryAdministratorCreateUpdate(d *schema.ResourceDa return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -131,7 +156,9 @@ func resourceArmSqlActiveDirectoryAdministratorDelete(d *schema.ResourceData, me resourceGroup := id.ResourceGroup serverName := id.Path["servers"] - _, err = client.Delete(ctx, resourceGroup, serverName) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + _, err = client.Delete(waitCtx, resourceGroup, serverName) if err != nil { return fmt.Errorf("Error deleting SQL AD administrator: %+v", err) } diff --git a/azurerm/resource_arm_sql_administrator_test.go b/azurerm/resource_arm_sql_administrator_test.go index dda4c421ff5e..24049a9280b4 100644 --- a/azurerm/resource_arm_sql_administrator_test.go +++ b/azurerm/resource_arm_sql_administrator_test.go @@ -39,6 +39,30 @@ func TestAccAzureRMSqlAdministrator_basic(t *testing.T) { }) } +func TestAccAzureRMSqlAdministrator_requiresImport(t *testing.T) { + resourceName := "azurerm_sql_active_directory_administrator.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlAdministratorDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSqlAdministrator_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlAdministratorExists(resourceName), + ), + }, + { + Config: testAccAzureRMSqlAdministrator_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_sql_active_directory_administrator"), + }, + }, + }) +} + func TestAccAzureRMSqlAdministrator_disappears(t *testing.T) { resourceName := "azurerm_sql_active_directory_administrator.test" ri := acctest.RandInt() @@ -160,6 +184,21 @@ resource "azurerm_sql_active_directory_administrator" "test" { `, rInt, location, rInt) } +func testAccAzureRMSqlAdministrator_requiresImport(rInt int, location string) string { + template := testAccAzureRMSqlAdministrator_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_sql_active_directory_administrator" "import" { + server_name = "${azurerm_sql_active_directory_administrator.test.server_name}" + resource_group_name = "${azurerm_sql_active_directory_administrator.test.resource_group_name}" + login = "${azurerm_sql_active_directory_administrator.test.login}" + tenant_id = "${azurerm_sql_active_directory_administrator.test.tenant_id}" + object_id = "${azurerm_sql_active_directory_administrator.test.object_id}" +} +`, template) +} + func testAccAzureRMSqlAdministrator_withUpdates(rInt int, location string) string { return fmt.Sprintf(` data "azurerm_client_config" "current" {} diff --git a/azurerm/resource_arm_sql_database.go b/azurerm/resource_arm_sql_database.go index e931b01616e9..63878023911e 100644 --- a/azurerm/resource_arm_sql_database.go +++ b/azurerm/resource_arm_sql_database.go @@ -1,6 +1,7 @@ package azurerm import ( + "context" "fmt" "log" "strings" @@ -12,6 +13,7 @@ import ( "github.com/hashicorp/terraform/helper/validation" "github.com/satori/go.uuid" "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/utils" ) @@ -24,6 +26,11 @@ func resourceArmSqlDatabase() *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": { @@ -309,11 +316,26 @@ func resourceArmSqlDatabase() *schema.Resource { func resourceArmSqlDatabaseCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).sqlDatabasesClient + ctx := meta.(*ArmClient).StopContext name := d.Get("name").(string) serverName := d.Get("server_name").(string) resourceGroup := d.Get("resource_group_name").(string) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, serverName, name, "") + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of SQL Database %q (Server %q / Resource Group %q): %+v", name, serverName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_sql_database", *resp.ID) + } + } + location := azureRMNormalizeLocation(d.Get("location").(string)) createMode := d.Get("create_mode").(string) tags := d.Get("tags").(map[string]interface{}) @@ -399,13 +421,14 @@ func resourceArmSqlDatabaseCreateUpdate(d *schema.ResourceData, meta interface{} properties.DatabaseProperties.RequestedServiceObjectiveID = nil } - ctx := meta.(*ArmClient).StopContext future, err := client.CreateOrUpdate(ctx, resourceGroup, serverName, name, properties) if err != nil { return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -425,7 +448,9 @@ func resourceArmSqlDatabaseCreateUpdate(d *schema.ResourceData, meta interface{} // for most imports client.Client.PollingDuration = 60 * time.Minute - err = importFuture.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = importFuture.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -535,7 +560,9 @@ func resourceArmSqlDatabaseDelete(d *schema.ResourceData, meta interface{}) erro serverName := id.Path["servers"] name := id.Path["databases"] - resp, err := client.Delete(ctx, resourceGroup, serverName, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resourceGroup, serverName, name) if err != nil { if utils.ResponseWasNotFound(resp) { return nil diff --git a/azurerm/resource_arm_sql_database_test.go b/azurerm/resource_arm_sql_database_test.go index 9f6fab40f3c5..777a6a202783 100644 --- a/azurerm/resource_arm_sql_database_test.go +++ b/azurerm/resource_arm_sql_database_test.go @@ -30,6 +30,29 @@ func TestAccAzureRMSqlDatabase_basic(t *testing.T) { }) } +func TestAccAzureRMSqlDatabase_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlDatabaseDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSqlDatabase_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlDatabaseExists("azurerm_sql_database.test"), + ), + }, + { + Config: testAccAzureRMSqlDatabase_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_sql_database"), + }, + }, + }) +} + func TestAccAzureRMSqlDatabase_disappears(t *testing.T) { resourceName := "azurerm_sql_database.test" ri := acctest.RandInt() @@ -384,6 +407,24 @@ resource "azurerm_sql_database" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMSqlDatabase_requiresImport(rInt int, location string) string { + template := testAccAzureRMSqlDatabase_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_sql_database" "import" { + name = "${azurerm_sql_database.test.name}" + resource_group_name = "${azurerm_sql_database.test.resource_group_name}" + server_name = "${azurerm_sql_database.test.server_name}" + location = "${azurerm_sql_database.test.location}" + edition = "${azurerm_sql_database.test.edition}" + collation = "${azurerm_sql_database.test.collation}" + max_size_bytes = "${azurerm_sql_database.test.max_size_bytes}" + requested_service_objective_name = "${azurerm_sql_database.test.requested_service_objective_name}" +} +`, template) +} + func testAccAzureRMSqlDatabase_withTags(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_sql_elasticpool.go b/azurerm/resource_arm_sql_elasticpool.go index 4efd0e516def..992f6fd553bd 100644 --- a/azurerm/resource_arm_sql_elasticpool.go +++ b/azurerm/resource_arm_sql_elasticpool.go @@ -1,6 +1,7 @@ package azurerm import ( + "context" "fmt" "log" "time" @@ -8,18 +9,24 @@ import ( "github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/2015-05-01-preview/sql" "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" ) func resourceArmSqlElasticPool() *schema.Resource { return &schema.Resource{ - Create: resourceArmSqlElasticPoolCreate, + Create: resourceArmSqlElasticPoolCreateUpdate, Read: resourceArmSqlElasticPoolRead, - Update: resourceArmSqlElasticPoolCreate, + Update: resourceArmSqlElasticPoolCreateUpdate, Delete: resourceArmSqlElasticPoolDelete, 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": { @@ -39,10 +46,14 @@ func resourceArmSqlElasticPool() *schema.Resource { }, "edition": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validateSqlElasticPoolEdition(), + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + string(sql.ElasticPoolEditionBasic), + string(sql.ElasticPoolEditionStandard), + string(sql.ElasticPoolEditionPremium), + }, false), }, "dtu": { @@ -78,7 +89,7 @@ func resourceArmSqlElasticPool() *schema.Resource { } } -func resourceArmSqlElasticPoolCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmSqlElasticPoolCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).sqlElasticPoolsClient ctx := meta.(*ArmClient).StopContext @@ -86,8 +97,23 @@ func resourceArmSqlElasticPoolCreate(d *schema.ResourceData, meta interface{}) e name := d.Get("name").(string) serverName := d.Get("server_name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, serverName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of SQL ElasticPool %q (Server %q / Resource Group %q): %+v", name, serverName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_sql_elasticpool", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) tags := d.Get("tags").(map[string]interface{}) elasticPool := sql.ElasticPool{ @@ -102,7 +128,9 @@ func resourceArmSqlElasticPoolCreate(d *schema.ResourceData, meta interface{}) e return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -171,7 +199,9 @@ func resourceArmSqlElasticPoolDelete(d *schema.ResourceData, meta interface{}) e return err } - _, err = client.Delete(ctx, resGroup, serverName, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + _, err = client.Delete(waitCtx, resGroup, serverName, name) return err } @@ -211,11 +241,3 @@ func parseArmSqlElasticPoolId(sqlElasticPoolId string) (string, string, string, return id.ResourceGroup, id.Path["servers"], id.Path["elasticPools"], nil } - -func validateSqlElasticPoolEdition() schema.SchemaValidateFunc { - return validation.StringInSlice([]string{ - string(sql.ElasticPoolEditionBasic), - string(sql.ElasticPoolEditionStandard), - string(sql.ElasticPoolEditionPremium), - }, false) -} diff --git a/azurerm/resource_arm_sql_elasticpool_test.go b/azurerm/resource_arm_sql_elasticpool_test.go index 518718910318..f6f02305783c 100644 --- a/azurerm/resource_arm_sql_elasticpool_test.go +++ b/azurerm/resource_arm_sql_elasticpool_test.go @@ -29,6 +29,29 @@ func TestAccAzureRMSqlElasticPool_basic(t *testing.T) { }) } +func TestAccAzureRMSqlElasticPool_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlElasticPoolDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSqlElasticPool_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlElasticPoolExists("azurerm_sql_elasticpool.test"), + ), + }, + { + Config: testAccAzureRMSqlElasticPool_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_sql_elasticpool"), + }, + }, + }) +} + func TestAccAzureRMSqlElasticPool_disappears(t *testing.T) { resourceName := "azurerm_sql_elasticpool.test" ri := acctest.RandInt() @@ -189,6 +212,23 @@ resource "azurerm_sql_elasticpool" "test" { `, rInt, location) } +func testAccAzureRMSqlElasticPool_requiresImport(rInt int, location string) string { + template := testAccAzureRMSqlElasticPool_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_sql_elasticpool" "import" { + name = "${azurerm_sql_elasticpool.test.name}" + resource_group_name = "${azurerm_sql_elasticpool.test.resource_group_name}" + location = "${azurerm_sql_elasticpool.test.location}" + server_name = "${azurerm_sql_elasticpool.test.server_name}" + edition = "${azurerm_sql_elasticpool.test.edition}" + dtu = "${azurerm_sql_elasticpool.test.dtu}" + pool_size = "${azurerm_sql_elasticpool.test.pool_size}" +} +`, template) +} + func testAccAzureRMSqlElasticPool_resizedDtu(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_sql_firewall_rule.go b/azurerm/resource_arm_sql_firewall_rule.go index 24f501f1e350..6c3f751dfdaf 100644 --- a/azurerm/resource_arm_sql_firewall_rule.go +++ b/azurerm/resource_arm_sql_firewall_rule.go @@ -1,11 +1,13 @@ package azurerm import ( + "context" "fmt" "log" "github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/2015-05-01-preview/sql" "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" ) @@ -56,6 +58,21 @@ func resourceArmSqlFirewallRuleCreateUpdate(d *schema.ResourceData, meta interfa name := d.Get("name").(string) serverName := d.Get("server_name").(string) resourceGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, serverName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Firewall Rule %q (Server %q / Resource Group %q): %+v", name, serverName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_sql_firewall_rule", *resp.ID) + } + } + startIPAddress := d.Get("start_ip_address").(string) endIPAddress := d.Get("end_ip_address").(string) @@ -66,7 +83,9 @@ func resourceArmSqlFirewallRuleCreateUpdate(d *schema.ResourceData, meta interfa }, } - _, err := client.CreateOrUpdate(ctx, resourceGroup, serverName, name, parameters) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, resourceGroup, serverName, name, parameters) if err != nil { return fmt.Errorf("Error creating SQL Firewall Rule: %+v", err) } @@ -127,7 +146,9 @@ func resourceArmSqlFirewallRuleDelete(d *schema.ResourceData, meta interface{}) serverName := id.Path["servers"] name := id.Path["firewallRules"] - resp, err := client.Delete(ctx, resourceGroup, serverName, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resourceGroup, serverName, name) if err != nil { if utils.ResponseWasNotFound(resp) { return nil diff --git a/azurerm/resource_arm_sql_firewall_rule_test.go b/azurerm/resource_arm_sql_firewall_rule_test.go index 391e1a9e1b06..596f3b525929 100644 --- a/azurerm/resource_arm_sql_firewall_rule_test.go +++ b/azurerm/resource_arm_sql_firewall_rule_test.go @@ -41,6 +41,30 @@ func TestAccAzureRMSqlFirewallRule_basic(t *testing.T) { }) } +func TestAccAzureRMSqlFirewallRule_requiresImport(t *testing.T) { + resourceName := "azurerm_sql_firewall_rule.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlFirewallRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSqlFirewallRule_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlFirewallRuleExists(resourceName), + ), + }, + { + Config: testAccAzureRMSqlFirewallRule_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_sql_firewall_rule"), + }, + }, + }) +} + func TestAccAzureRMSqlFirewallRule_disappears(t *testing.T) { resourceName := "azurerm_sql_firewall_rule.test" ri := acctest.RandInt() @@ -172,6 +196,21 @@ resource "azurerm_sql_firewall_rule" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMSqlFirewallRule_requiresImport(rInt int, location string) string { + template := testAccAzureRMSqlFirewallRule_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_sql_firewall_rule" "import" { + name = "${azurerm_sql_firewall_rule.test.name}" + resource_group_name = "${azurerm_sql_firewall_rule.test.resource_group_name}" + server_name = "${azurerm_sql_firewall_rule.test.server_name}" + start_ip_address = "${azurerm_sql_firewall_rule.test.start_ip_address}" + end_ip_address = "${azurerm_sql_firewall_rule.test.end_ip_address}" +} +`, template) +} + func testAccAzureRMSqlFirewallRule_withUpdates(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_sql_server.go b/azurerm/resource_arm_sql_server.go index 343cc672bca8..d0618e71485d 100644 --- a/azurerm/resource_arm_sql_server.go +++ b/azurerm/resource_arm_sql_server.go @@ -1,14 +1,17 @@ package azurerm import ( + "context" "fmt" "log" "regexp" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/2015-05-01-preview/sql" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -21,6 +24,11 @@ func resourceArmSqlServer() *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": { @@ -75,6 +83,21 @@ func resourceArmSqlServerCreateUpdate(d *schema.ResourceData, meta interface{}) name := d.Get("name").(string) resGroup := d.Get("resource_group_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 SQL Server %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_sql_server", *resp.ID) + } + } + location := azureRMNormalizeLocation(d.Get("location").(string)) adminUsername := d.Get("administrator_login").(string) adminPassword := d.Get("administrator_login_password").(string) @@ -98,7 +121,9 @@ func resourceArmSqlServerCreateUpdate(d *schema.ResourceData, meta interface{}) return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if response.WasConflict(future.Response()) { @@ -175,7 +200,9 @@ func resourceArmSqlServerDelete(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("Error deleting SQL Server %s: %+v", name, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } diff --git a/azurerm/resource_arm_sql_server_test.go b/azurerm/resource_arm_sql_server_test.go index bdc9187b1f9c..aa37c81ee311 100644 --- a/azurerm/resource_arm_sql_server_test.go +++ b/azurerm/resource_arm_sql_server_test.go @@ -80,6 +80,29 @@ func TestAccAzureRMSqlServer_basic(t *testing.T) { }) } +func TestAccAzureRMSqlServer_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlServerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSqlServer_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlServerExists("azurerm_sql_server.test"), + ), + }, + { + Config: testAccAzureRMSqlServer_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_sql_server"), + }, + }, + }) +} + func TestAccAzureRMSqlServer_disappears(t *testing.T) { resourceName := "azurerm_sql_server.test" ri := acctest.RandInt() @@ -235,6 +258,22 @@ resource "azurerm_sql_server" "test" { `, rInt, location, rInt) } +func testAccAzureRMSqlServer_requiresImport(rInt int, location string) string { + template := testAccAzureRMSqlServer_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_sql_server" "import" { + name = "${azurerm_sql_server.test.name}" + resource_group_name = "${azurerm_sql_server.test.resource_group_name}" + location = "${azurerm_sql_server.test.location}" + version = "${azurerm_sql_server.test.version}" + administrator_login = "${azurerm_sql_server.test.administrator_login}" + administrator_login_password = "${azurerm_sql_server.test.administrator_login_password}" +} +`, template) +} + func testAccAzureRMSqlServer_withTags(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_sql_virtual_network_rule.go b/azurerm/resource_arm_sql_virtual_network_rule.go index 8e24f2844c15..adfcc88bca26 100644 --- a/azurerm/resource_arm_sql_virtual_network_rule.go +++ b/azurerm/resource_arm_sql_virtual_network_rule.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -23,6 +24,11 @@ func resourceArmSqlVirtualNetworkRule() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 10), + Update: schema.DefaultTimeout(time.Minute * 10), + Delete: schema.DefaultTimeout(time.Minute * 10), + }, Schema: map[string]*schema.Schema{ "name": { @@ -71,6 +77,9 @@ func resourceArmSqlVirtualNetworkRuleCreateUpdate(d *schema.ResourceData, meta i }, } + timeout := d.Timeout(tf.TimeoutForCreateUpdate(d)) + waitCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() _, err := client.CreateOrUpdate(ctx, resourceGroup, serverName, name, parameters) if err != nil { return fmt.Errorf("Error creating SQL Virtual Network Rule %q (SQL Server: %q, Resource Group: %q): %+v", name, serverName, resourceGroup, err) @@ -78,11 +87,13 @@ func resourceArmSqlVirtualNetworkRuleCreateUpdate(d *schema.ResourceData, meta i //Wait for the provisioning state to become ready log.Printf("[DEBUG] Waiting for SQL Virtual Network Rule %q (SQL Server: %q, Resource Group: %q) to become ready: %+v", name, serverName, resourceGroup, err) + waitCtx, cancel = context.WithTimeout(ctx, timeout) + defer cancel() stateConf := &resource.StateChangeConf{ Pending: []string{"Initializing", "InProgress", "Unknown", "ResponseNotFound"}, Target: []string{"Ready"}, - Refresh: sqlVirtualNetworkStateStatusCodeRefreshFunc(ctx, client, resourceGroup, serverName, name), - Timeout: 10 * time.Minute, + Refresh: sqlVirtualNetworkStateStatusCodeRefreshFunc(waitCtx, client, resourceGroup, serverName, name), + Timeout: timeout, MinTimeout: 1 * time.Minute, ContinuousTargetOccurence: 5, } @@ -159,7 +170,9 @@ func resourceArmSqlVirtualNetworkRuleDelete(d *schema.ResourceData, meta interfa return fmt.Errorf("Error deleting SQL Virtual Network Rule %q (SQL Server: %q, Resource Group: %q): %+v", name, serverName, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { if response.WasNotFound(future.Response()) { return nil diff --git a/azurerm/resource_arm_sql_virtual_network_rule_test.go b/azurerm/resource_arm_sql_virtual_network_rule_test.go index d8659ff07f6a..dbfeb9ee2d73 100644 --- a/azurerm/resource_arm_sql_virtual_network_rule_test.go +++ b/azurerm/resource_arm_sql_virtual_network_rule_test.go @@ -12,11 +12,6 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) -/* - ---Testing for Success--- - Test a basic SQL virtual network rule configuration setup and update scenario, and - validate that new property is set correctly. -*/ func TestAccAzureRMSqlVirtualNetworkRule_basic(t *testing.T) { resourceName := "azurerm_sql_virtual_network_rule.test" ri := acctest.RandInt() @@ -46,11 +41,30 @@ func TestAccAzureRMSqlVirtualNetworkRule_basic(t *testing.T) { }) } -/* - ---Testing for Success--- - Test an update to the SQL Virtual Network Rule to connect to a different subnet, and - validate that new subnet is set correctly. -*/ +func TestAccAzureRMSqlVirtualNetworkRule_requiresImport(t *testing.T) { + resourceName := "azurerm_sql_virtual_network_rule.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlVirtualNetworkRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSqlVirtualNetworkRule_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlVirtualNetworkRuleExists(resourceName), + ), + }, + { + Config: testAccAzureRMSqlVirtualNetworkRule_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_sql_virtual_network_rule"), + }, + }, + }) +} + func TestAccAzureRMSqlVirtualNetworkRule_switchSubnets(t *testing.T) { resourceName := "azurerm_sql_virtual_network_rule.test" ri := acctest.RandInt() @@ -85,9 +99,6 @@ func TestAccAzureRMSqlVirtualNetworkRule_switchSubnets(t *testing.T) { }) } -/* - ---Testing for Success--- -*/ func TestAccAzureRMSqlVirtualNetworkRule_disappears(t *testing.T) { resourceName := "azurerm_sql_virtual_network_rule.test" ri := acctest.RandInt() @@ -110,11 +121,6 @@ func TestAccAzureRMSqlVirtualNetworkRule_disappears(t *testing.T) { }) } -/* - --Testing for Success-- - Test if we are able to create a vnet without the SQL endpoint, but SQL rule - is still applied since the endpoint validation will be set to false. -*/ func TestAccAzureRMSqlVirtualNetworkRule_IgnoreEndpointValid(t *testing.T) { resourceName := "azurerm_sql_virtual_network_rule.test" ri := acctest.RandInt() @@ -135,11 +141,6 @@ func TestAccAzureRMSqlVirtualNetworkRule_IgnoreEndpointValid(t *testing.T) { }) } -/* - --Testing for Failure-- - Test if we are able to create a vnet with out the SQL endpoint, but SQL rule - is still applied since the endpoint validation will be set to false. -*/ func TestAccAzureRMSqlVirtualNetworkRule_IgnoreEndpointInvalid(t *testing.T) { ri := acctest.RandInt() config := testAccAzureRMSqlVirtualNetworkRule_ignoreEndpointInvalid(ri, testLocation()) @@ -157,11 +158,6 @@ func TestAccAzureRMSqlVirtualNetworkRule_IgnoreEndpointInvalid(t *testing.T) { }) } -/* - --Testing for Success-- - Test if we are able to create multiple subnets and connect multiple subnets to the - SQL server. -*/ func TestAccAzureRMSqlVirtualNetworkRule_multipleSubnets(t *testing.T) { resourceName1 := "azurerm_sql_virtual_network_rule.rule1" resourceName2 := "azurerm_sql_virtual_network_rule.rule2" @@ -186,10 +182,6 @@ func TestAccAzureRMSqlVirtualNetworkRule_multipleSubnets(t *testing.T) { }) } -/* - --Testing for Failure-- - Validation Function Tests - Invalid Name Validations -*/ func TestResourceAzureRMSqlVirtualNetworkRule_invalidNameValidation(t *testing.T) { cases := []struct { Value string @@ -247,10 +239,6 @@ func TestResourceAzureRMSqlVirtualNetworkRule_invalidNameValidation(t *testing.T } } -/* - --Testing for Success-- - Validation Function Tests - (Barely) Valid Name Validations -*/ func TestResourceAzureRMSqlVirtualNetworkRule_validNameValidation(t *testing.T) { cases := []struct { Value string @@ -312,9 +300,6 @@ func TestResourceAzureRMSqlVirtualNetworkRule_validNameValidation(t *testing.T) } } -/* - Test Check function to assert if a rule exists or not. -*/ func testCheckAzureRMSqlVirtualNetworkRuleExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] @@ -342,9 +327,6 @@ func testCheckAzureRMSqlVirtualNetworkRuleExists(name string) resource.TestCheck } } -/* - Test Check function to delete a rule. -*/ func testCheckAzureRMSqlVirtualNetworkRuleDestroy(s *terraform.State) error { for _, rs := range s.RootModule().Resources { if rs.Type != "azurerm_sql_virtual_network_rule" { @@ -373,9 +355,6 @@ func testCheckAzureRMSqlVirtualNetworkRuleDestroy(s *terraform.State) error { return nil } -/* - Test Check function to assert if that a rule gets deleted. -*/ func testCheckAzureRMSqlVirtualNetworkRuleDisappears(name string) resource.TestCheckFunc { return func(s *terraform.State) error { // Ensure we have enough information in state to look up in API @@ -416,10 +395,6 @@ func testCheckAzureRMSqlVirtualNetworkRuleDisappears(name string) resource.TestC } } -/* - (This test configuration is intended to succeed.) - Basic Provisioning Configuration -*/ func testAccAzureRMSqlVirtualNetworkRule_basic(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { @@ -457,11 +432,21 @@ resource "azurerm_sql_virtual_network_rule" "test" { `, rInt, location, rInt, rInt, rInt, rInt, rInt) } -/* - (This test configuration is intended to succeed.) - Basic Provisioning Update Configuration (all other properties would recreate the rule) - ignore_missing_vnet_service_endpoint (false ==> true) -*/ +func testAccAzureRMSqlVirtualNetworkRule_requiresImport(rInt int, location string) string { + template := testAccAzureRMSqlVirtualNetworkRule_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_sql_virtual_network_rule" "import" { + name = "${azurerm_sql_virtual_network_rule.test.name}" + resource_group_name = "${azurerm_sql_virtual_network_rule.test.resource_group_name}" + server_name = "${azurerm_sql_virtual_network_rule.test.server_name}" + subnet_id = "${azurerm_sql_virtual_network_rule.test.subnet_id}" + ignore_missing_vnet_service_endpoint = "${azurerm_sql_virtual_network_rule.test.ignore_missing_vnet_service_endpoint}" +} +`, template) +} + func testAccAzureRMSqlVirtualNetworkRule_withUpdates(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { @@ -499,11 +484,6 @@ resource "azurerm_sql_virtual_network_rule" "test" { `, rInt, location, rInt, rInt, rInt, rInt, rInt) } -/* - (This test configuration is intended to succeed.) - This test is designed to set up a scenario where a user would want to update the subnet - on a given SQL virtual network rule. This configuration sets up the resources initially. -*/ func testAccAzureRMSqlVirtualNetworkRule_subnetSwitchPre(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { @@ -547,12 +527,6 @@ resource "azurerm_sql_virtual_network_rule" "test" { `, rInt, location, rInt, rInt, rInt, rInt, rInt, rInt) } -/* - (This test configuration is intended to succeed.) - This test is designed to set up a scenario where a user would want to update the subnet - on a given SQL virtual network rule. This configuration contains the update from - azurerm_subnet.test1 to azurerm_subnet.test2. -*/ func testAccAzureRMSqlVirtualNetworkRule_subnetSwitchPost(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { @@ -596,12 +570,6 @@ resource "azurerm_sql_virtual_network_rule" "test" { `, rInt, location, rInt, rInt, rInt, rInt, rInt, rInt) } -/* - (This test configuration is intended to succeed.) - Succeeds because subnet's service_endpoints does not include 'Microsoft.Sql' and the SQL - virtual network rule is set to *not* validate that the service_endpoint includes that value. - The endpoint is purposefully set to Microsoft.Storage. -*/ func testAccAzureRMSqlVirtualNetworkRule_ignoreEndpointValid(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { @@ -639,12 +607,6 @@ resource "azurerm_sql_virtual_network_rule" "test" { `, rInt, location, rInt, rInt, rInt, rInt, rInt) } -/* - (This test configuration is intended to fail.) - Fails because subnet's service_endpoints does not include 'Microsoft.Sql' and the SQL - virtual network rule is set to validate that the service_endpoint includes that value. - The endpoint is purposefully set to Microsoft.Storage. -*/ func testAccAzureRMSqlVirtualNetworkRule_ignoreEndpointInvalid(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { @@ -682,11 +644,6 @@ resource "azurerm_sql_virtual_network_rule" "test" { `, rInt, location, rInt, rInt, rInt, rInt, rInt) } -/* - (This test configuration is intended to succeed.) - This configuration sets up 3 subnets in 2 different virtual networks, and adds - SQL virtual network rules for all 3 subnets to the SQL server. -*/ func testAccAzureRMSqlVirtualNetworkRule_multipleSubnets(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_storage_account.go b/azurerm/resource_arm_storage_account.go index b1740bcc0714..4a87427f4f45 100644 --- a/azurerm/resource_arm_storage_account.go +++ b/azurerm/resource_arm_storage_account.go @@ -1,15 +1,18 @@ package azurerm import ( + "context" "errors" "fmt" "log" "regexp" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2017-10-01/storage" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" + tf "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -21,12 +24,16 @@ func resourceArmStorageAccount() *schema.Resource { Read: resourceArmStorageAccountRead, Update: resourceArmStorageAccountUpdate, Delete: resourceArmStorageAccountDelete, - Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, MigrateState: resourceStorageAccountMigrateState, SchemaVersion: 2, + 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": { @@ -299,36 +306,25 @@ func resourceArmStorageAccount() *schema.Resource { } } -func validateAzureRMStorageAccountTags(v interface{}, _ string) (ws []string, es []error) { - tagsMap := v.(map[string]interface{}) +func resourceArmStorageAccountCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).storageServiceClient + ctx := meta.(*ArmClient).StopContext - if len(tagsMap) > 15 { - es = append(es, errors.New("a maximum of 15 tags can be applied to each ARM resource")) - } + resourceGroupName := d.Get("resource_group_name").(string) + storageAccountName := d.Get("name").(string) - for k, v := range tagsMap { - if len(k) > 128 { - es = append(es, fmt.Errorf("the maximum length for a tag key is 128 characters: %q is %d characters", k, len(k))) + if d.IsNewResource() { + resp, err := client.GetProperties(ctx, resourceGroupName, storageAccountName) + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Storage Account %q (Resource Group %q): %+v", storageAccountName, resourceGroupName, err) } - value, err := tagValueToString(v) - if err != nil { - es = append(es, err) - } else if len(value) > 256 { - es = append(es, fmt.Errorf("the maximum length for a tag value is 256 characters: the value for %q is %d characters", k, len(value))) + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_storage_account", *resp.ID) } } - return ws, es -} - -func resourceArmStorageAccountCreate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*ArmClient).storageServiceClient - - resourceGroupName := d.Get("resource_group_name").(string) - storageAccountName := d.Get("name").(string) accountKind := d.Get("account_kind").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) tags := d.Get("tags").(map[string]interface{}) enableBlobEncryption := d.Get("enable_blob_encryption").(bool) @@ -393,13 +389,14 @@ func resourceArmStorageAccountCreate(d *schema.ResourceData, meta interface{}) e } // Create - ctx := meta.(*ArmClient).StopContext future, err := client.Create(ctx, resourceGroupName, storageAccountName, parameters) if err != nil { return fmt.Errorf("Error creating Azure Storage Account %q: %+v", storageAccountName, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for Azure Storage Account %q to be created: %+v", storageAccountName, err) } @@ -423,8 +420,8 @@ func resourceArmStorageAccountCreate(d *schema.ResourceData, meta interface{}) e // and idempotent operation for CreateOrUpdate. In particular updating all of the parameters // available requires a call to Update per parameter... func resourceArmStorageAccountUpdate(d *schema.ResourceData, meta interface{}) error { - ctx := meta.(*ArmClient).StopContext client := meta.(*ArmClient).storageServiceClient + ctx := meta.(*ArmClient).StopContext id, err := parseAzureResourceID(d.Id()) if err != nil { return err @@ -453,7 +450,9 @@ func resourceArmStorageAccountUpdate(d *schema.ResourceData, meta interface{}) e opts := storage.AccountUpdateParameters{ Sku: &sku, } - _, err := client.Update(ctx, resourceGroupName, storageAccountName, opts) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutUpdate)) + defer cancel() + _, err := client.Update(waitCtx, resourceGroupName, storageAccountName, opts) if err != nil { return fmt.Errorf("Error updating Azure Storage Account type %q: %+v", storageAccountName, err) } @@ -470,7 +469,9 @@ func resourceArmStorageAccountUpdate(d *schema.ResourceData, meta interface{}) e }, } - _, err := client.Update(ctx, resourceGroupName, storageAccountName, opts) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutUpdate)) + defer cancel() + _, err := client.Update(waitCtx, resourceGroupName, storageAccountName, opts) if err != nil { return fmt.Errorf("Error updating Azure Storage Account access_tier %q: %+v", storageAccountName, err) } @@ -484,7 +485,10 @@ func resourceArmStorageAccountUpdate(d *schema.ResourceData, meta interface{}) e opts := storage.AccountUpdateParameters{ Tags: expandTags(tags), } - _, err := client.Update(ctx, resourceGroupName, storageAccountName, opts) + + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutUpdate)) + defer cancel() + _, err := client.Update(waitCtx, resourceGroupName, storageAccountName, opts) if err != nil { return fmt.Errorf("Error updating Azure Storage Account tags %q: %+v", storageAccountName, err) } @@ -521,7 +525,9 @@ func resourceArmStorageAccountUpdate(d *schema.ResourceData, meta interface{}) e d.SetPartial("enable_file_encryption") } - _, err := client.Update(ctx, resourceGroupName, storageAccountName, opts) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutUpdate)) + defer cancel() + _, err := client.Update(waitCtx, resourceGroupName, storageAccountName, opts) if err != nil { return fmt.Errorf("Error updating Azure Storage Account Encryption %q: %+v", storageAccountName, err) } @@ -535,7 +541,9 @@ func resourceArmStorageAccountUpdate(d *schema.ResourceData, meta interface{}) e }, } - _, err := client.Update(ctx, resourceGroupName, storageAccountName, opts) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutUpdate)) + defer cancel() + _, err := client.Update(waitCtx, resourceGroupName, storageAccountName, opts) if err != nil { return fmt.Errorf("Error updating Azure Storage Account Custom Domain %q: %+v", storageAccountName, err) } @@ -549,7 +557,10 @@ func resourceArmStorageAccountUpdate(d *schema.ResourceData, meta interface{}) e EnableHTTPSTrafficOnly: &enableHTTPSTrafficOnly, }, } - _, err := client.Update(ctx, resourceGroupName, storageAccountName, opts) + + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutUpdate)) + defer cancel() + _, err := client.Update(waitCtx, resourceGroupName, storageAccountName, opts) if err != nil { return fmt.Errorf("Error updating Azure Storage Account enable_https_traffic_only %q: %+v", storageAccountName, err) } @@ -563,7 +574,10 @@ func resourceArmStorageAccountUpdate(d *schema.ResourceData, meta interface{}) e opts := storage.AccountUpdateParameters{ Identity: storageAccountIdentity, } - _, err := client.Update(ctx, resourceGroupName, storageAccountName, opts) + + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutUpdate)) + defer cancel() + _, err := client.Update(waitCtx, resourceGroupName, storageAccountName, opts) if err != nil { return fmt.Errorf("Error updating Azure Storage Account identity %q: %+v", storageAccountName, err) } @@ -577,7 +591,10 @@ func resourceArmStorageAccountUpdate(d *schema.ResourceData, meta interface{}) e NetworkRuleSet: networkRules, }, } - _, err := client.Update(ctx, resourceGroupName, storageAccountName, opts) + + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutUpdate)) + defer cancel() + _, err := client.Update(waitCtx, resourceGroupName, storageAccountName, opts) if err != nil { return fmt.Errorf("Error updating Azure Storage Account network_rules %q: %+v", storageAccountName, err) } @@ -732,7 +749,9 @@ func resourceArmStorageAccountDelete(d *schema.ResourceData, meta interface{}) e name := id.Path["storageAccounts"] resGroup := id.ResourceGroup - _, err = client.Delete(ctx, resGroup, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + _, err = client.Delete(waitCtx, resGroup, name) if err != nil { return fmt.Errorf("Error issuing AzureRM delete request for storage account %q: %+v", name, err) } @@ -928,3 +947,26 @@ func flattenAzureRmStorageAccountIdentity(identity *storage.Identity) []interfac return []interface{}{result} } + +func validateAzureRMStorageAccountTags(v interface{}, _ string) (ws []string, es []error) { + tagsMap := v.(map[string]interface{}) + + if len(tagsMap) > 15 { + es = append(es, errors.New("a maximum of 15 tags can be applied to each ARM resource")) + } + + for k, v := range tagsMap { + if len(k) > 128 { + es = append(es, fmt.Errorf("the maximum length for a tag key is 128 characters: %q is %d characters", k, len(k))) + } + + value, err := tagValueToString(v) + if err != nil { + es = append(es, err) + } else if len(value) > 256 { + es = append(es, fmt.Errorf("the maximum length for a tag value is 256 characters: the value for %q is %d characters", k, len(value))) + } + } + + return ws, es +} diff --git a/azurerm/resource_arm_storage_account_test.go b/azurerm/resource_arm_storage_account_test.go index 3b8a167f275b..4bc9a4067a9d 100644 --- a/azurerm/resource_arm_storage_account_test.go +++ b/azurerm/resource_arm_storage_account_test.go @@ -89,6 +89,32 @@ func TestAccAzureRMStorageAccount_basic(t *testing.T) { }) } +func TestAccAzureRMStorageAccount_requiresImport(t *testing.T) { + resourceName := "azurerm_storage_account.testsa" + ri := acctest.RandInt() + rs := acctest.RandString(4) + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMStorageAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMStorageAccount_basic(ri, rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageAccountExists(resourceName), + ), + }, + + { + Config: testAccAzureRMStorageAccount_requiresImport(ri, rs, location), + ExpectError: testRequiresImportError("azurerm_storage_account"), + }, + }, + }) +} + func TestAccAzureRMStorageAccount_premium(t *testing.T) { resourceName := "azurerm_storage_account.testsa" ri := acctest.RandInt() @@ -574,6 +600,25 @@ resource "azurerm_storage_account" "testsa" { `, rInt, location, rString) } +func testAccAzureRMStorageAccount_requiresImport(rInt int, rString string, location string) string { + template := testAccAzureRMStorageAccount_basic(rInt, rString, location) + return fmt.Sprintf(` +%s + +resource "azurerm_storage_account" "import" { + name = "${azurem_storage_account.test.name}" + resource_group_name = "${azurem_storage_account.test.resource_group_name}" + location = "${azurem_storage_account.test.location}" + account_tier = "${azurem_storage_account.test.account_tier}" + account_replication_type = "${azurem_storage_account.test.account_replication_type}" + + tags { + environment = "production" + } +} +`, template) +} + func testAccAzureRMStorageAccount_premium(rInt int, rString string, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "testrg" { diff --git a/azurerm/resource_arm_storage_blob.go b/azurerm/resource_arm_storage_blob.go index 22af74b011c3..48aed76a9d67 100644 --- a/azurerm/resource_arm_storage_blob.go +++ b/azurerm/resource_arm_storage_blob.go @@ -2,6 +2,7 @@ package azurerm import ( "bytes" + "context" "crypto/rand" "encoding/base64" "fmt" @@ -12,10 +13,13 @@ import ( "runtime" "strings" "sync" + "time" "github.com/Azure/azure-sdk-for-go/storage" "github.com/Azure/go-autorest/autorest/azure" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" ) func resourceArmStorageBlob() *schema.Resource { @@ -29,6 +33,11 @@ func resourceArmStorageBlob() *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": { @@ -48,10 +57,13 @@ func resourceArmStorageBlob() *schema.Resource { ForceNew: true, }, "type": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validateArmStorageBlobType, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "block", + "page", + }, false), }, "size": { Type: schema.TypeInt, @@ -78,10 +90,6 @@ func resourceArmStorageBlob() *schema.Resource { ForceNew: true, ConflictsWith: []string{"source"}, }, - "url": { - Type: schema.TypeString, - Computed: true, - }, "parallelism": { Type: schema.TypeInt, Optional: true, @@ -96,53 +104,14 @@ func resourceArmStorageBlob() *schema.Resource { ForceNew: true, ValidateFunc: validateArmStorageBlobAttempts, }, + "url": { + Type: schema.TypeString, + Computed: true, + }, }, } } -func validateArmStorageBlobParallelism(v interface{}, k string) (ws []string, errors []error) { - value := v.(int) - - if value <= 0 { - errors = append(errors, fmt.Errorf("Blob Parallelism %q is invalid, must be greater than 0", value)) - } - - return -} - -func validateArmStorageBlobAttempts(v interface{}, k string) (ws []string, errors []error) { - value := v.(int) - - if value <= 0 { - errors = append(errors, fmt.Errorf("Blob Attempts %q is invalid, must be greater than 0", value)) - } - - return -} - -func validateArmStorageBlobSize(v interface{}, k string) (ws []string, errors []error) { - value := v.(int) - - if value%512 != 0 { - errors = append(errors, fmt.Errorf("Blob Size %q is invalid, must be a multiple of 512", value)) - } - - return -} - -func validateArmStorageBlobType(v interface{}, k string) (ws []string, errors []error) { - value := strings.ToLower(v.(string)) - validTypes := map[string]struct{}{ - "block": {}, - "page": {}, - } - - if _, ok := validTypes[value]; !ok { - errors = append(errors, fmt.Errorf("Blob type %q is invalid, must be %q or %q", value, "block", "page")) - } - return -} - func resourceArmStorageBlobCreate(d *schema.ResourceData, meta interface{}) error { armClient := meta.(*ArmClient) ctx := armClient.StopContext @@ -151,7 +120,9 @@ func resourceArmStorageBlobCreate(d *schema.ResourceData, meta interface{}) erro resourceGroupName := d.Get("resource_group_name").(string) storageAccountName := d.Get("storage_account_name").(string) - blobClient, accountExists, err := armClient.getBlobStorageClientForStorageAccount(ctx, resourceGroupName, storageAccountName) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + blobClient, accountExists, err := armClient.getBlobStorageClientForStorageAccount(waitCtx, resourceGroupName, storageAccountName) if err != nil { return err } @@ -160,14 +131,32 @@ func resourceArmStorageBlobCreate(d *schema.ResourceData, meta interface{}) erro } name := d.Get("name").(string) - blobType := d.Get("type").(string) containerName := d.Get("storage_container_name").(string) + + container := blobClient.GetContainerReference(containerName) + exists, err := container.Exists() + if err != nil { + return fmt.Errorf("Error checking if container %q exists in storage account %q: %+v", containerName, storageAccountName, err) + } + + if !exists { + return fmt.Errorf("Container %q doesn't exist within Storage Account %q", containerName, storageAccountName) + } + + blob := container.GetBlobReference(name) + exists, err = blob.Exists() + if err != nil { + return fmt.Errorf("Error checking if blob %q exists in container %q: %+v", name, containerName, err) + } + if exists { + return tf.ImportAsExistsError("azurerm_storage_blob", name) + } + + blobType := d.Get("type").(string) sourceUri := d.Get("source_uri").(string) contentType := d.Get("content_type").(string) log.Printf("[INFO] Creating blob %q in container %q within storage account %q", name, containerName, storageAccountName) - container := blobClient.GetContainerReference(containerName) - blob := container.GetBlobReference(name) if sourceUri != "" { options := &storage.CopyOptions{} @@ -188,7 +177,6 @@ func resourceArmStorageBlobCreate(d *schema.ResourceData, meta interface{}) erro if source != "" { parallelism := d.Get("parallelism").(int) attempts := d.Get("attempts").(int) - if err := resourceArmStorageBlobBlockUploadFromSource(containerName, name, source, contentType, blobClient, parallelism, attempts); err != nil { return fmt.Errorf("Error creating storage blob on Azure: %s", err) } @@ -198,14 +186,12 @@ func resourceArmStorageBlobCreate(d *schema.ResourceData, meta interface{}) erro if source != "" { parallelism := d.Get("parallelism").(int) attempts := d.Get("attempts").(int) - if err := resourceArmStorageBlobPageUploadFromSource(containerName, name, source, contentType, blobClient, parallelism, attempts); err != nil { return fmt.Errorf("Error creating storage blob on Azure: %s", err) } } else { size := int64(d.Get("size").(int)) options := &storage.PutBlobOptions{} - blob.Properties.ContentLength = size blob.Properties.ContentType = contentType err := blob.PutPageBlob(options) @@ -242,6 +228,7 @@ func resourceArmStorageBlobPageUploadFromSource(container, name, source, content } options := &storage.PutBlobOptions{} + // we don't bother re-checking if they exist here, since this is done in the Create containerRef := client.GetContainerReference(container) blob := containerRef.GetBlobReference(name) blob.Properties.ContentLength = blobSize @@ -561,7 +548,9 @@ func resourceArmStorageBlobUpdate(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("Unable to determine Resource Group for Storage Account %q", id.storageAccountName) } - blobClient, accountExists, err := armClient.getBlobStorageClientForStorageAccount(ctx, *resourceGroup, id.storageAccountName) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutUpdate)) + defer cancel() + blobClient, accountExists, err := armClient.getBlobStorageClientForStorageAccount(waitCtx, *resourceGroup, id.storageAccountName) if err != nil { return fmt.Errorf("Error getting storage account %s: %+v", id.storageAccountName, err) } @@ -649,6 +638,7 @@ func resourceArmStorageBlobRead(d *schema.ResourceData, meta interface{}) error if url == "" { log.Printf("[INFO] URL for %q is empty", id.blobName) } + d.Set("url", url) return nil @@ -672,7 +662,9 @@ func resourceArmStorageBlobDelete(d *schema.ResourceData, meta interface{}) erro return nil } - blobClient, accountExists, err := armClient.getBlobStorageClientForStorageAccount(ctx, *resourceGroup, id.storageAccountName) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + blobClient, accountExists, err := armClient.getBlobStorageClientForStorageAccount(waitCtx, *resourceGroup, id.storageAccountName) if err != nil { return err } @@ -748,3 +740,33 @@ func determineResourceGroupForStorageAccount(accountName string, client *ArmClie return nil, nil } + +func validateArmStorageBlobAttempts(v interface{}, k string) (ws []string, errors []error) { + value := v.(int) + + if value <= 0 { + errors = append(errors, fmt.Errorf("Blob Attempts %q is invalid, must be greater than 0", value)) + } + + return +} + +func validateArmStorageBlobSize(v interface{}, k string) (ws []string, errors []error) { + value := v.(int) + + if value%512 != 0 { + errors = append(errors, fmt.Errorf("Blob Size %q is invalid, must be a multiple of 512", value)) + } + + return +} + +func validateArmStorageBlobParallelism(v interface{}, k string) (ws []string, errors []error) { + value := v.(int) + + if value <= 0 { + errors = append(errors, fmt.Errorf("Blob Parallelism %q is invalid, must be greater than 0", value)) + } + + return +} diff --git a/azurerm/resource_arm_storage_blob_test.go b/azurerm/resource_arm_storage_blob_test.go index 324f8179f55d..8c9a8434d66e 100644 --- a/azurerm/resource_arm_storage_blob_test.go +++ b/azurerm/resource_arm_storage_blob_test.go @@ -15,42 +15,6 @@ import ( "github.com/hashicorp/terraform/terraform" ) -func TestResourceAzureRMStorageBlobType_validation(t *testing.T) { - cases := []struct { - Value string - ErrCount int - }{ - { - Value: "unknown", - ErrCount: 1, - }, - { - Value: "page", - ErrCount: 0, - }, - { - Value: "block", - ErrCount: 0, - }, - { - Value: "BLOCK", - ErrCount: 0, - }, - { - Value: "Block", - ErrCount: 0, - }, - } - - for _, tc := range cases { - _, errors := validateArmStorageBlobType(tc.Value, "azurerm_storage_blob") - - if len(errors) != tc.ErrCount { - t.Fatalf("Expected the Azure RM Storage Blob type to trigger a validation error") - } - } -} - func TestResourceAzureRMStorageBlobSize_validation(t *testing.T) { cases := []struct { Value int @@ -170,6 +134,30 @@ func TestAccAzureRMStorageBlob_basic(t *testing.T) { }) } +func TestAccAzureRMStorageBlob_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + rs := strings.ToLower(acctest.RandString(11)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMStorageBlobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMStorageBlob_basic(ri, rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageBlobExists("azurerm_storage_blob.test"), + ), + }, + { + Config: testAccAzureRMStorageBlob_requiresImport(ri, rs, location), + ExpectError: testRequiresImportError("azurerm_storage_blob"), + }, + }, + }) +} + func TestAccAzureRMStorageBlob_disappears(t *testing.T) { resourceName := "azurerm_storage_blob.test" ri := acctest.RandInt() @@ -597,6 +585,22 @@ resource "azurerm_storage_blob" "test" { `, rInt, location, rString) } +func testAccAzureRMStorageBlob_requiresImport(rInt int, rString string, location string) string { + template := testAccAzureRMStorageBlob_basic(rInt, rString, location) + return fmt.Sprintf(` +%s + +resource "azurerm_storage_blob" "import" { + name = "${azurerm_storage_blob.test.name}" + resource_group_name = "${azurerm_storage_blob.test.resource_group_name}" + storage_account_name = "${azurerm_storage_blob.test.storage_account_name}" + storage_container_name = "${azurerm_storage_blob.test.storage_container_name}" + type = "${azurerm_storage_blob.test.type}" + size = "${azurerm_storage_blob.test.size}" +} +`, template) +} + func testAccAzureRMStorageBlobBlock_source(rInt int, rString string, sourceBlobName string, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_storage_container.go b/azurerm/resource_arm_storage_container.go index 2fcd0b394e3b..c3730ba7f3c3 100644 --- a/azurerm/resource_arm_storage_container.go +++ b/azurerm/resource_arm_storage_container.go @@ -1,6 +1,7 @@ package azurerm import ( + "context" "fmt" "log" "net/url" @@ -12,6 +13,8 @@ import ( "github.com/Azure/go-autorest/autorest/azure" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" ) func resourceArmStorageContainer() *schema.Resource { @@ -24,6 +27,10 @@ func resourceArmStorageContainer() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Schema: map[string]*schema.Schema{ "name": { @@ -39,11 +46,16 @@ func resourceArmStorageContainer() *schema.Resource { ForceNew: true, }, "container_access_type": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: "private", - ValidateFunc: validateArmStorageContainerAccessType, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "private", + ValidateFunc: validation.StringInSlice( + []string{ + "blob", + "container", + "private", + }, true), }, "properties": { @@ -54,39 +66,6 @@ func resourceArmStorageContainer() *schema.Resource { } } -//Following the naming convention as laid out in the docs -func validateArmStorageContainerName(v interface{}, k string) (ws []string, errors []error) { - value := v.(string) - if !regexp.MustCompile(`^\$root$|^[0-9a-z-]+$`).MatchString(value) { - errors = append(errors, fmt.Errorf( - "only lowercase alphanumeric characters and hyphens allowed in %q: %q", - k, value)) - } - if len(value) < 3 || len(value) > 63 { - errors = append(errors, fmt.Errorf( - "%q must be between 3 and 63 characters: %q", k, value)) - } - if regexp.MustCompile(`^-`).MatchString(value) { - errors = append(errors, fmt.Errorf( - "%q cannot begin with a hyphen: %q", k, value)) - } - return -} - -func validateArmStorageContainerAccessType(v interface{}, k string) (ws []string, errors []error) { - value := strings.ToLower(v.(string)) - validTypes := map[string]struct{}{ - "private": {}, - "blob": {}, - "container": {}, - } - - if _, ok := validTypes[value]; !ok { - errors = append(errors, fmt.Errorf("Storage container access type %q is invalid, must be %q, %q or %q", value, "private", "blob", "page")) - } - return -} - func resourceArmStorageContainerCreate(d *schema.ResourceData, meta interface{}) error { armClient := meta.(*ArmClient) ctx := armClient.StopContext @@ -95,7 +74,9 @@ func resourceArmStorageContainerCreate(d *schema.ResourceData, meta interface{}) resourceGroupName := d.Get("resource_group_name").(string) storageAccountName := d.Get("storage_account_name").(string) - blobClient, accountExists, err := armClient.getBlobStorageClientForStorageAccount(ctx, resourceGroupName, storageAccountName) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + blobClient, accountExists, err := armClient.getBlobStorageClientForStorageAccount(waitCtx, resourceGroupName, storageAccountName) if err != nil { return err } @@ -110,9 +91,17 @@ func resourceArmStorageContainerCreate(d *schema.ResourceData, meta interface{}) accessType = storage.ContainerAccessType(d.Get("container_access_type").(string)) } - log.Printf("[INFO] Creating container %q in storage account %q.", name, storageAccountName) reference := blobClient.GetContainerReference(name) + exists, err := reference.Exists() + if err != nil { + return fmt.Errorf("Error checking if container %q exists in storage account %q: %+v", name, storageAccountName, err) + } + if exists { + return tf.ImportAsExistsError("azurerm_storage_container", name) + } + + log.Printf("[INFO] Creating container %q in storage account %q.", name, storageAccountName) err = resource.Retry(120*time.Second, checkContainerIsCreated(reference)) if err != nil { return fmt.Errorf("Error creating container %q in storage account %q: %s", name, storageAccountName, err) @@ -132,8 +121,6 @@ func resourceArmStorageContainerCreate(d *schema.ResourceData, meta interface{}) return resourceArmStorageContainerRead(d, meta) } -// resourceAzureStorageContainerRead does all the necessary API calls to -// read the status of the storage container off Azure. func resourceArmStorageContainerRead(d *schema.ResourceData, meta interface{}) error { armClient := meta.(*ArmClient) ctx := armClient.StopContext @@ -210,8 +197,6 @@ func resourceArmStorageContainerRead(d *schema.ResourceData, meta interface{}) e return nil } -// resourceAzureStorageContainerDelete does all the necessary API calls to -// delete a storage container off Azure. func resourceArmStorageContainerDelete(d *schema.ResourceData, meta interface{}) error { armClient := meta.(*ArmClient) ctx := armClient.StopContext @@ -227,10 +212,11 @@ func resourceArmStorageContainerDelete(d *schema.ResourceData, meta interface{}) } if resourceGroup == nil { log.Printf("Cannot locate Resource Group for Storage Account %q (presuming it's gone) - removing from state", id.storageAccountName) - return nil } - blobClient, accountExists, err := armClient.getBlobStorageClientForStorageAccount(ctx, *resourceGroup, id.storageAccountName) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + blobClient, accountExists, err := armClient.getBlobStorageClientForStorageAccount(waitCtx, *resourceGroup, id.storageAccountName) if err != nil { return err } @@ -287,3 +273,22 @@ func parseStorageContainerID(input string, environment azure.Environment) (*stor } return &id, nil } + +//Following the naming convention as laid out in the docs +func validateArmStorageContainerName(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if !regexp.MustCompile(`^\$root$|^[0-9a-z-]+$`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "only lowercase alphanumeric characters and hyphens allowed in %q: %q", + k, value)) + } + if len(value) < 3 || len(value) > 63 { + errors = append(errors, fmt.Errorf( + "%q must be between 3 and 63 characters: %q", k, value)) + } + if regexp.MustCompile(`^-`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "%q cannot begin with a hyphen: %q", k, value)) + } + return +} diff --git a/azurerm/resource_arm_storage_container_test.go b/azurerm/resource_arm_storage_container_test.go index 8b841cd14a64..4251f297e19f 100644 --- a/azurerm/resource_arm_storage_container_test.go +++ b/azurerm/resource_arm_storage_container_test.go @@ -40,6 +40,32 @@ func TestAccAzureRMStorageContainer_basic(t *testing.T) { }) } +func TestAccAzureRMStorageContainer_requiresImport(t *testing.T) { + var c storage.Container + + ri := acctest.RandInt() + rs := strings.ToLower(acctest.RandString(11)) + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMStorageContainerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMStorageContainer_basic(ri, rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageContainerExists("azurerm_storage_container.test", &c), + ), + }, + { + Config: testAccAzureRMStorageContainer_requiresImport(ri, rs, location), + ExpectError: testRequiresImportError("azurerm_storage_container"), + }, + }, + }) +} + func TestAccAzureRMStorageContainer_disappears(t *testing.T) { var c storage.Container @@ -285,6 +311,20 @@ resource "azurerm_storage_container" "test" { `, rInt, location, rString) } +func testAccAzureRMStorageContainer_requiresImport(rInt int, rString string, location string) string { + template := testAccAzureRMStorageContainer_basic(rInt, rString, location) + return fmt.Sprintf(` +%s + +resource "azurerm_storage_container" "import" { + name = "${azurerm_storage_container.test.name}" + resource_group_name = "${azurerm_storage_container.test.resource_group_name}" + storage_account_name = "${azurerm_storage_container.test.storage_account_name}" + container_access_type = "${azurerm_storage_container.test.container_access_type}" +} +`, template) +} + func testAccAzureRMStorageContainer_root(rInt int, rString string, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_storage_queue.go b/azurerm/resource_arm_storage_queue.go index 8f3bcf9cf3fd..1889fa4d410d 100644 --- a/azurerm/resource_arm_storage_queue.go +++ b/azurerm/resource_arm_storage_queue.go @@ -1,26 +1,33 @@ package azurerm import ( + "context" "fmt" "log" "net/url" "regexp" "strings" + "time" "github.com/Azure/azure-sdk-for-go/storage" "github.com/hashicorp/terraform/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" ) func resourceArmStorageQueue() *schema.Resource { return &schema.Resource{ - Create: resourceArmStorageQueueCreate, - Read: resourceArmStorageQueueRead, - Delete: resourceArmStorageQueueDelete, + Create: resourceArmStorageQueueCreate, + Read: resourceArmStorageQueueRead, + Delete: resourceArmStorageQueueDelete, + SchemaVersion: 1, + MigrateState: resourceStorageQueueMigrateState, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, - SchemaVersion: 1, - MigrateState: resourceStorageQueueMigrateState, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 30), + Delete: schema.DefaultTimeout(time.Minute * 30), + }, Schema: map[string]*schema.Schema{ "name": { @@ -39,35 +46,6 @@ func resourceArmStorageQueue() *schema.Resource { } } -func validateArmStorageQueueName(v interface{}, k string) (ws []string, errors []error) { - value := v.(string) - - if !regexp.MustCompile(`^[a-z0-9-]+$`).MatchString(value) { - errors = append(errors, fmt.Errorf( - "only lowercase alphanumeric characters and hyphens allowed in %q", k)) - } - - if regexp.MustCompile(`^-`).MatchString(value) { - errors = append(errors, fmt.Errorf("%q cannot start with a hyphen", k)) - } - - if regexp.MustCompile(`-$`).MatchString(value) { - errors = append(errors, fmt.Errorf("%q cannot end with a hyphen", k)) - } - - if len(value) > 63 { - errors = append(errors, fmt.Errorf( - "%q cannot be longer than 63 characters", k)) - } - - if len(value) < 3 { - errors = append(errors, fmt.Errorf( - "%q must be at least 3 characters", k)) - } - - return -} - func resourceArmStorageQueueCreate(d *schema.ResourceData, meta interface{}) error { armClient := meta.(*ArmClient) ctx := armClient.StopContext @@ -77,7 +55,9 @@ func resourceArmStorageQueueCreate(d *schema.ResourceData, meta interface{}) err resourceGroupName := d.Get("resource_group_name").(string) storageAccountName := d.Get("storage_account_name").(string) - queueClient, accountExists, err := armClient.getQueueServiceClientForStorageAccount(ctx, resourceGroupName, storageAccountName) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + queueClient, accountExists, err := armClient.getQueueServiceClientForStorageAccount(waitCtx, resourceGroupName, storageAccountName) if err != nil { return err } @@ -85,8 +65,17 @@ func resourceArmStorageQueueCreate(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("Storage Account %q Not Found", storageAccountName) } - log.Printf("[INFO] Creating queue %q in storage account %q", name, storageAccountName) queueReference := queueClient.GetQueueReference(name) + exists, err := queueReference.Exists() + if err != nil { + return fmt.Errorf("Error checking for the existence of queue %q in storage account %q: %+v", name, storageAccountName, err) + } + + if exists { + return tf.ImportAsExistsError("azurerm_storage_queue", name) + } + + log.Printf("[INFO] Creating queue %q in storage account %q", name, storageAccountName) options := &storage.QueueServiceOptions{} err = queueReference.Create(options) if err != nil { @@ -167,10 +156,13 @@ func resourceArmStorageQueueDelete(d *schema.ResourceData, meta interface{}) err return nil } - queueClient, accountExists, err := armClient.getQueueServiceClientForStorageAccount(ctx, *resourceGroup, id.storageAccountName) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + queueClient, accountExists, err := armClient.getQueueServiceClientForStorageAccount(waitCtx, *resourceGroup, id.storageAccountName) if err != nil { return err } + if !accountExists { log.Printf("[INFO]Storage Account %q doesn't exist so the blob won't exist", id.storageAccountName) return nil @@ -212,3 +204,32 @@ func parseStorageQueueID(input string) (*storageQueueId, error) { return nil, nil } + +func validateArmStorageQueueName(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + if !regexp.MustCompile(`^[a-z0-9-]+$`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "only lowercase alphanumeric characters and hyphens allowed in %q", k)) + } + + if regexp.MustCompile(`^-`).MatchString(value) { + errors = append(errors, fmt.Errorf("%q cannot start with a hyphen", k)) + } + + if regexp.MustCompile(`-$`).MatchString(value) { + errors = append(errors, fmt.Errorf("%q cannot end with a hyphen", k)) + } + + if len(value) > 63 { + errors = append(errors, fmt.Errorf( + "%q cannot be longer than 63 characters", k)) + } + + if len(value) < 3 { + errors = append(errors, fmt.Errorf( + "%q must be at least 3 characters", k)) + } + + return +} diff --git a/azurerm/resource_arm_storage_queue_test.go b/azurerm/resource_arm_storage_queue_test.go index caec84139db0..703b314bd42b 100644 --- a/azurerm/resource_arm_storage_queue_test.go +++ b/azurerm/resource_arm_storage_queue_test.go @@ -76,6 +76,30 @@ func TestAccAzureRMStorageQueue_basic(t *testing.T) { }) } +func TestAccAzureRMStorageQueue_requiresImport(t *testing.T) { + ri := acctest.RandInt() + rs := strings.ToLower(acctest.RandString(11)) + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMStorageQueueDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMStorageQueue_basic(ri, rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageQueueExists("azurerm_storage_queue.test"), + ), + }, + { + Config: testAccAzureRMStorageQueue_requiresImport(ri, rs, location), + ExpectError: testRequiresImportError("azurerm_storage_queue"), + }, + }, + }) +} + func testCheckAzureRMStorageQueueExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -178,3 +202,16 @@ resource "azurerm_storage_queue" "test" { } `, rInt, location, rString, rInt) } + +func testAccAzureRMStorageQueue_requiresImport(rInt int, rString string, location string) string { + template := testAccAzureRMStorageQueue_basic(rInt, rString, location) + return fmt.Sprintf(` +%s + +resource "azurerm_storage_queue" "import" { + name = "${azurerm_storage_queue.test.name}" + resource_group_name = "${azurerm_storage_queue.test.resource_group_name}" + storage_account_name = "${azurerm_storage_queue.test.storage_account_name}" +} +`, template) +} diff --git a/azurerm/resource_arm_storage_share.go b/azurerm/resource_arm_storage_share.go index 9235903091c2..0d13fbcf3887 100644 --- a/azurerm/resource_arm_storage_share.go +++ b/azurerm/resource_arm_storage_share.go @@ -1,12 +1,15 @@ package azurerm import ( + "context" "fmt" "log" "regexp" "strings" + "time" "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/Azure/azure-sdk-for-go/storage" "github.com/hashicorp/terraform/helper/schema" @@ -23,6 +26,11 @@ func resourceArmStorageShare() *schema.Resource { }, SchemaVersion: 1, MigrateState: resourceStorageShareMigrateState, + 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": { @@ -57,7 +65,9 @@ func resourceArmStorageShareCreate(d *schema.ResourceData, meta interface{}) err resourceGroupName := d.Get("resource_group_name").(string) storageAccountName := d.Get("storage_account_name").(string) - fileClient, accountExists, err := armClient.getFileServiceClientForStorageAccount(ctx, resourceGroupName, storageAccountName) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() + fileClient, accountExists, err := armClient.getFileServiceClientForStorageAccount(waitCtx, resourceGroupName, storageAccountName) if err != nil { return err } @@ -69,8 +79,18 @@ func resourceArmStorageShareCreate(d *schema.ResourceData, meta interface{}) err metaData := make(map[string]string) // TODO: support MetaData options := &storage.FileRequestOptions{} - log.Printf("[INFO] Creating share %q in storage account %q", name, storageAccountName) reference := fileClient.GetShareReference(name) + exists, err := reference.Exists() + if err != nil { + return fmt.Errorf("Error checking if the Share %q already exists within Storage Account %q: %+v", name, storageAccountName, err) + } + + id := fmt.Sprintf("%s/%s/%s", name, resourceGroupName, storageAccountName) + if exists { + return tf.ImportAsExistsError("azurerm_storage_share", id) + } + + log.Printf("[INFO] Creating share %q in storage account %q", name, storageAccountName) err = reference.Create(options) log.Printf("[INFO] Setting share %q metadata in storage account %q", name, storageAccountName) @@ -81,9 +101,12 @@ func resourceArmStorageShareCreate(d *schema.ResourceData, meta interface{}) err reference.Properties = storage.ShareProperties{ Quota: d.Get("quota").(int), } - reference.SetProperties(options) + err = reference.SetProperties(options) + if err != nil { + return fmt.Errorf("Error setting properties on Share %q within Storage Account %q: %+v", name, storageAccountName, err) + } - d.SetId(fmt.Sprintf("%s/%s/%s", name, resourceGroupName, storageAccountName)) + d.SetId(id) return resourceArmStorageShareRead(d, meta) } @@ -148,7 +171,9 @@ func resourceArmStorageShareUpdate(d *schema.ResourceData, meta interface{}) err resourceGroupName := id[1] storageAccountName := id[2] - fileClient, accountExists, err := armClient.getFileServiceClientForStorageAccount(ctx, resourceGroupName, storageAccountName) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutUpdate)) + defer cancel() + fileClient, accountExists, err := armClient.getFileServiceClientForStorageAccount(waitCtx, resourceGroupName, storageAccountName) if err != nil { return err } @@ -164,7 +189,10 @@ func resourceArmStorageShareUpdate(d *schema.ResourceData, meta interface{}) err reference.Properties = storage.ShareProperties{ Quota: d.Get("quota").(int), } - reference.SetProperties(options) + err = reference.SetProperties(options) + if err != nil { + return fmt.Errorf("Error setting properties on Share %q within Storage Account %q: %+v", name, storageAccountName, err) + } return resourceArmStorageShareRead(d, meta) } @@ -181,12 +209,14 @@ func resourceArmStorageShareDelete(d *schema.ResourceData, meta interface{}) err resourceGroupName := id[1] storageAccountName := id[2] - fileClient, accountExists, err := armClient.getFileServiceClientForStorageAccount(ctx, resourceGroupName, storageAccountName) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + fileClient, accountExists, err := armClient.getFileServiceClientForStorageAccount(waitCtx, resourceGroupName, storageAccountName) if err != nil { return err } if !accountExists { - log.Printf("[INFO]Storage Account %q doesn't exist so the file won't exist", storageAccountName) + log.Printf("[INFO] Storage Account %q doesn't exist so the file won't exist", storageAccountName) return nil } diff --git a/azurerm/resource_arm_storage_share_test.go b/azurerm/resource_arm_storage_share_test.go index 689d41344841..6eddcfcc4097 100644 --- a/azurerm/resource_arm_storage_share_test.go +++ b/azurerm/resource_arm_storage_share_test.go @@ -40,6 +40,33 @@ func TestAccAzureRMStorageShare_basic(t *testing.T) { }) } +func TestAccAzureRMStorageShare_requiresImport(t *testing.T) { + var sS storage.Share + + ri := acctest.RandInt() + rs := strings.ToLower(acctest.RandString(11)) + location := testLocation() + resourceName := "azurerm_storage_share.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMStorageShareDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMStorageShare_basic(ri, rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageShareExists(resourceName, &sS), + ), + }, + { + Config: testAccAzureRMStorageShare_requiresImport(ri, rs, location), + ExpectError: testRequiresImportError("azurerm_storage_share"), + }, + }, + }) +} + func TestAccAzureRMStorageShare_disappears(t *testing.T) { var sS storage.Share @@ -257,6 +284,19 @@ resource "azurerm_storage_share" "test" { }`, rInt, location, rString) } +func testAccAzureRMStorageShare_requiresImport(rInt int, rString string, location string) string { + template := testAccAzureRMStorageShare_basic(rInt, rString, location) + return fmt.Sprintf(` +%s + +resource "azurerm_storage_share" "import" { + name = "${azurerm_storage_share.test.name}" + resource_group_name = "${azurerm_storage_share.test.resource_group_name}" + storage_account_name = "${azurerm_storage_share.test.storage_account_name}" +} +`, template) +} + func testAccAzureRMStorageShare_updateQuota(rInt int, rString string, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_storage_table.go b/azurerm/resource_arm_storage_table.go index 63ab4fdc7d04..61267526ea74 100644 --- a/azurerm/resource_arm_storage_table.go +++ b/azurerm/resource_arm_storage_table.go @@ -9,18 +9,19 @@ import ( "github.com/Azure/azure-sdk-for-go/storage" "github.com/hashicorp/terraform/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" ) func resourceArmStorageTable() *schema.Resource { return &schema.Resource{ - Create: resourceArmStorageTableCreate, - Read: resourceArmStorageTableRead, - Delete: resourceArmStorageTableDelete, + Create: resourceArmStorageTableCreate, + Read: resourceArmStorageTableRead, + Delete: resourceArmStorageTableDelete, + SchemaVersion: 1, + MigrateState: resourceStorageTableMigrateState, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, - SchemaVersion: 1, - MigrateState: resourceStorageTableMigrateState, Schema: map[string]*schema.Schema{ "name": { @@ -39,22 +40,6 @@ func resourceArmStorageTable() *schema.Resource { } } -func validateArmStorageTableName(v interface{}, k string) (ws []string, errors []error) { - value := v.(string) - if value == "table" { - errors = append(errors, fmt.Errorf( - "Table Storage %q cannot use the word `table`: %q", - k, value)) - } - if !regexp.MustCompile(`^[A-Za-z][A-Za-z0-9]{2,62}$`).MatchString(value) { - errors = append(errors, fmt.Errorf( - "Table Storage %q cannot begin with a numeric character, only alphanumeric characters are allowed and must be between 3 and 63 characters long: %q", - k, value)) - } - - return -} - func resourceArmStorageTableCreate(d *schema.ResourceData, meta interface{}) error { armClient := meta.(*ArmClient) ctx := armClient.StopContext @@ -72,10 +57,20 @@ func resourceArmStorageTableCreate(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("Storage Account %q Not Found", storageAccountName) } - table := tableClient.GetTableReference(name) + // firstly check if the table already exists and needs importing + tables, err := tableClient.QueryTables(storage.MinimalMetadata, &storage.QueryTablesOptions{}) + if err != nil { + return fmt.Errorf("Failed to retrieve storage tables in account %q: %s", name, err) + } - log.Printf("[INFO] Creating table %q in storage account %q.", name, storageAccountName) + for _, t := range tables.Tables { + if t.Name == name { + return tf.ImportAsExistsError("azurerm_storage_table", t.Name) + } + } + log.Printf("[INFO] Creating table %q in storage account %q.", name, storageAccountName) + table := tableClient.GetTableReference(name) timeout := uint(60) options := &storage.TableOptions{} err = table.Create(timeout, storage.NoMetadata, options) @@ -119,9 +114,8 @@ func resourceArmStorageTableRead(d *schema.ResourceData, meta interface{}) error return nil } - metaDataLevel := storage.MinimalMetadata options := &storage.QueryTablesOptions{} - tables, err := tableClient.QueryTables(metaDataLevel, options) + tables, err := tableClient.QueryTables(storage.MinimalMetadata, options) if err != nil { return fmt.Errorf("Failed to retrieve Tables in Storage Account %q: %s", id.tableName, err) } @@ -212,3 +206,19 @@ func parseStorageTableID(input string) (*storageTableId, error) { return nil, nil } + +func validateArmStorageTableName(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value == "table" { + errors = append(errors, fmt.Errorf( + "Table Storage %q cannot use the word `table`: %q", + k, value)) + } + if !regexp.MustCompile(`^[A-Za-z][A-Za-z0-9]{2,62}$`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "Table Storage %q cannot begin with a numeric character, only alphanumeric characters are allowed and must be between 3 and 63 characters long: %q", + k, value)) + } + + return +} diff --git a/azurerm/resource_arm_storage_table_test.go b/azurerm/resource_arm_storage_table_test.go index 4014787ef35f..905291b9fe41 100644 --- a/azurerm/resource_arm_storage_table_test.go +++ b/azurerm/resource_arm_storage_table_test.go @@ -40,6 +40,32 @@ func TestAccAzureRMStorageTable_basic(t *testing.T) { }) } +func TestAccAzureRMStorageTable_requiresImport(t *testing.T) { + var table storage.Table + + ri := acctest.RandInt() + rs := strings.ToLower(acctest.RandString(11)) + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMStorageTableDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMStorageTable_basic(ri, rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageTableExists("azurerm_storage_table.test", &table), + ), + }, + { + Config: testAccAzureRMStorageTable_requiresImport(ri, rs, location), + ExpectError: testRequiresImportError("azurerm_storage_table"), + }, + }, + }) +} + func TestAccAzureRMStorageTable_disappears(t *testing.T) { var table storage.Table @@ -253,3 +279,16 @@ resource "azurerm_storage_table" "test" { } `, rInt, location, rString, rInt) } + +func testAccAzureRMStorageTable_requiresImport(rInt int, rString string, location string) string { + template := testAccAzureRMStorageTable_basic(rInt, rString, location) + return fmt.Sprintf(` +%s + +resource "azurerm_storage_table" "import" { + name = "${azurerm_storage_table.test.name}" + resource_group_name = "${azurerm_storage_table.test.resource_group_name}" + storage_account_name = "${azurerm_storage_table.test.storage_account_name}" +} +`, template) +} diff --git a/azurerm/resource_arm_subnet.go b/azurerm/resource_arm_subnet.go index 82c993d53113..2556163b16d7 100644 --- a/azurerm/resource_arm_subnet.go +++ b/azurerm/resource_arm_subnet.go @@ -1,11 +1,14 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" "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" ) @@ -13,13 +16,18 @@ var subnetResourceName = "azurerm_subnet" func resourceArmSubnet() *schema.Resource { return &schema.Resource{ - Create: resourceArmSubnetCreate, + Create: resourceArmSubnetCreateUpdate, Read: resourceArmSubnetRead, - Update: resourceArmSubnetCreate, + Update: resourceArmSubnetCreateUpdate, Delete: resourceArmSubnetDelete, 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": { @@ -68,7 +76,7 @@ func resourceArmSubnet() *schema.Resource { } } -func resourceArmSubnetCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmSubnetCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).subnetClient ctx := meta.(*ArmClient).StopContext @@ -77,6 +85,21 @@ func resourceArmSubnetCreate(d *schema.ResourceData, meta interface{}) error { name := d.Get("name").(string) vnetName := d.Get("virtual_network_name").(string) resGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, vnetName, name, "") + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Subnet %q (Virtual Network %q / Resource Group %q): %+v", name, vnetName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_subnet", *resp.ID) + } + } + addressPrefix := d.Get("address_prefix").(string) azureRMLockByName(vnetName, virtualNetworkResourceName) @@ -133,7 +156,9 @@ func resourceArmSubnetCreate(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error Creating/Updating Subnet %q (VN %q / Resource Group %q): %+v", name, vnetName, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for completion of Subnet %q (VN %q / Resource Group %q): %+v", name, vnetName, resGroup, err) } @@ -247,8 +272,11 @@ func resourceArmSubnetDelete(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error deleting Subnet %q (VN %q / Resource Group %q): %+v", name, vnetName, resGroup, err) } - if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("Error waiting for completion for Subnet %q (VN %q / Resource Group %q): %+v", name, vnetName, resGroup, err) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) + if err != nil { + return fmt.Errorf("Error waiting for deletion of Subnet %q (VN %q / Resource Group %q): %+v", name, vnetName, resGroup, err) } return nil diff --git a/azurerm/resource_arm_subnet_test.go b/azurerm/resource_arm_subnet_test.go index af95e34a828f..f84fbc93a793 100644 --- a/azurerm/resource_arm_subnet_test.go +++ b/azurerm/resource_arm_subnet_test.go @@ -14,7 +14,6 @@ import ( ) func TestAccAzureRMSubnet_basic(t *testing.T) { - ri := acctest.RandInt() config := testAccAzureRMSubnet_basic(ri, testLocation()) @@ -33,6 +32,29 @@ func TestAccAzureRMSubnet_basic(t *testing.T) { }) } +func TestAccAzureRMSubnet_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSubnetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSubnet_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSubnetExists("azurerm_subnet.test"), + ), + }, + { + Config: testAccAzureRMSubnet_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_subnet"), + }, + }, + }) +} + func TestAccAzureRMSubnet_routeTableUpdate(t *testing.T) { ri := acctest.RandInt() location := testLocation() @@ -368,6 +390,20 @@ resource "azurerm_subnet" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMSubnet_requiresImport(rInt int, location string) string { + template := testAccAzureRMSubnet_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_subnet" "import" { + name = "${azurerm_subnet.test.name}" + resource_group_name = "${azurerm_subnet.test.resource_group_name}" + virtual_network_name = "${azurerm_subnet.test.virtual_network_name}" + address_prefix = "${azurerm_subnet.test.address_prefix}" +} +`, template) +} + func testAccAzureRMSubnet_routeTable(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_template_deployment.go b/azurerm/resource_arm_template_deployment.go index f4a662de17f2..96f91f6afdbd 100644 --- a/azurerm/resource_arm_template_deployment.go +++ b/azurerm/resource_arm_template_deployment.go @@ -13,15 +13,24 @@ import ( "github.com/hashicorp/terraform/helper/resource" "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" ) func resourceArmTemplateDeployment() *schema.Resource { return &schema.Resource{ - Create: resourceArmTemplateDeploymentCreate, + Create: resourceArmTemplateDeploymentCreateUpdate, Read: resourceArmTemplateDeploymentRead, - Update: resourceArmTemplateDeploymentCreate, + Update: resourceArmTemplateDeploymentCreateUpdate, Delete: resourceArmTemplateDeploymentDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 60), + Update: schema.DefaultTimeout(time.Minute * 60), + Delete: schema.DefaultTimeout(time.Minute * 60), + }, Schema: map[string]*schema.Schema{ "name": { @@ -70,16 +79,29 @@ func resourceArmTemplateDeployment() *schema.Resource { } } -func resourceArmTemplateDeploymentCreate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*ArmClient) - deployClient := client.deploymentsClient - ctx := client.StopContext +func resourceArmTemplateDeploymentCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).deploymentsClient + ctx := meta.(*ArmClient).StopContext name := d.Get("name").(string) resourceGroup := d.Get("resource_group_name").(string) - deploymentMode := d.Get("deployment_mode").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Template Deployment %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_template_deployment", *resp.ID) + } + } log.Printf("[INFO] preparing arguments for AzureRM Template Deployment creation.") + deploymentMode := d.Get("deployment_mode").(string) properties := resources.DeploymentProperties{ Mode: resources.DeploymentMode(deploymentMode), } @@ -121,17 +143,19 @@ func resourceArmTemplateDeploymentCreate(d *schema.ResourceData, meta interface{ Properties: &properties, } - future, err := deployClient.CreateOrUpdate(ctx, resourceGroup, name, deployment) + future, err := client.CreateOrUpdate(ctx, resourceGroup, name, deployment) if err != nil { return fmt.Errorf("Error creating deployment: %+v", err) } - err = future.WaitForCompletionRef(ctx, deployClient.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error creating deployment: %+v", err) } - read, err := deployClient.Get(ctx, resourceGroup, name) + read, err := client.Get(ctx, resourceGroup, name) if err != nil { return err } @@ -145,9 +169,8 @@ func resourceArmTemplateDeploymentCreate(d *schema.ResourceData, meta interface{ } func resourceArmTemplateDeploymentRead(d *schema.ResourceData, meta interface{}) error { - client := meta.(*ArmClient) - deployClient := client.deploymentsClient - ctx := client.StopContext + client := meta.(*ArmClient).deploymentsClient + ctx := meta.(*ArmClient).StopContext id, err := parseAzureResourceID(d.Id()) if err != nil { @@ -159,7 +182,7 @@ func resourceArmTemplateDeploymentRead(d *schema.ResourceData, meta interface{}) name = id.Path["Deployments"] } - resp, err := deployClient.Get(ctx, resourceGroup, name) + resp, err := client.Get(ctx, resourceGroup, name) if err != nil { if utils.ResponseWasNotFound(resp.Response) { d.SetId("") @@ -168,52 +191,78 @@ func resourceArmTemplateDeploymentRead(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error making Read request on Azure RM Template Deployment %q (Resource Group %q): %+v", name, resourceGroup, err) } + templ, err := client.ExportTemplate(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("Error exporting ARM Template for Template Deployment %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + d.Set("name", name) + d.Set("resource_group_name", resourceGroup) + + if props := resp.Properties; props != nil { + outputs := flattenTemplateDeploymentOutputs(props.Outputs) + if err := d.Set("outputs", outputs); err != nil { + return fmt.Errorf("Error setting `outputs`: %+v", err) + } + + d.Set("deployment_mode", string(props.Mode)) + } + + template, err := flattenTemplateBody(templ.Template) + if err != nil { + return fmt.Errorf("Error flattening `template_body`: %s", err) + } + d.Set("template_body", template) + + return nil +} + +func flattenTemplateDeploymentOutputs(input interface{}) map[string]string { outputs := make(map[string]string, 0) - if outs := resp.Properties.Outputs; outs != nil { - outsVal := outs.(map[string]interface{}) - if len(outsVal) > 0 { - for key, output := range outsVal { - log.Printf("[DEBUG] Processing deployment output %s", key) - outputMap := output.(map[string]interface{}) - outputValue, ok := outputMap["value"] - if !ok { - log.Printf("[DEBUG] No value - skipping") - continue - } - outputType, ok := outputMap["type"] - if !ok { - log.Printf("[DEBUG] No type - skipping") - continue - } - - var outputValueString string - switch strings.ToLower(outputType.(string)) { - case "bool": - outputValueString = strconv.FormatBool(outputValue.(bool)) - - case "string": - outputValueString = outputValue.(string) - - case "int": - outputValueString = fmt.Sprint(outputValue) - - default: - log.Printf("[WARN] Ignoring output %s: Outputs of type %s are not currently supported in azurerm_template_deployment.", - key, outputType) - continue - } - outputs[key] = outputValueString - } + if input == nil { + return outputs + } + + outsVal := input.(map[string]interface{}) + for key, output := range outsVal { + log.Printf("[DEBUG] Processing deployment output %s", key) + outputMap := output.(map[string]interface{}) + outputValue, ok := outputMap["value"] + if !ok { + log.Printf("[DEBUG] No value - skipping") + continue + } + outputType, ok := outputMap["type"] + if !ok { + log.Printf("[DEBUG] No type - skipping") + continue + } + + var outputValueString string + switch strings.ToLower(outputType.(string)) { + case "bool": + outputValueString = strconv.FormatBool(outputValue.(bool)) + + case "string": + outputValueString = outputValue.(string) + + case "int": + outputValueString = fmt.Sprint(outputValue) + + default: + log.Printf("[WARN] Ignoring output %s: Outputs of type %s are not currently supported in azurerm_template_deployment.", + key, outputType) + continue } + outputs[key] = outputValueString } - return d.Set("outputs", outputs) + return outputs } func resourceArmTemplateDeploymentDelete(d *schema.ResourceData, meta interface{}) error { - client := meta.(*ArmClient) - deployClient := client.deploymentsClient - ctx := client.StopContext + client := meta.(*ArmClient).deploymentsClient + ctx := meta.(*ArmClient).StopContext id, err := parseAzureResourceID(d.Id()) if err != nil { @@ -225,12 +274,13 @@ func resourceArmTemplateDeploymentDelete(d *schema.ResourceData, meta interface{ name = id.Path["Deployments"] } - _, err = deployClient.Delete(ctx, resourceGroup, name) + _, err = client.Delete(ctx, resourceGroup, name) if err != nil { return err } - return waitForTemplateDeploymentToBeDeleted(ctx, deployClient, resourceGroup, name) + timeout := d.Timeout(schema.TimeoutDelete) + return waitForTemplateDeploymentToBeDeleted(ctx, client, resourceGroup, name, timeout) } // TODO: move this out into the new `helpers` structure @@ -252,6 +302,21 @@ func expandTemplateBody(template string) (map[string]interface{}, error) { return templateBody, nil } +func flattenTemplateBody(input interface{}) (*string, error) { + if input == nil { + return nil, nil + } + + v := input.(map[string]interface{}) + bytes, err := json.Marshal(v) + if err != nil { + return nil, fmt.Errorf("Error marshalling the ARM Template: %+v", err) + } + + str := string(bytes) + return &str, nil +} + func normalizeJson(jsonString interface{}) string { if jsonString == nil || jsonString == "" { return "" @@ -265,14 +330,16 @@ func normalizeJson(jsonString interface{}) string { return string(b[:]) } -func waitForTemplateDeploymentToBeDeleted(ctx context.Context, client resources.DeploymentsClient, resourceGroup, name string) error { +func waitForTemplateDeploymentToBeDeleted(ctx context.Context, client resources.DeploymentsClient, resourceGroup, name string, timeout time.Duration) error { + waitCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() // we can't use the Waiter here since the API returns a 200 once it's deleted which is considered a polling status code.. log.Printf("[DEBUG] Waiting for Template Deployment (%q in Resource Group %q) to be deleted", name, resourceGroup) stateConf := &resource.StateChangeConf{ Pending: []string{"200"}, Target: []string{"404"}, - Refresh: templateDeploymentStateStatusCodeRefreshFunc(ctx, client, resourceGroup, name), - Timeout: 40 * time.Minute, + Refresh: templateDeploymentStateStatusCodeRefreshFunc(waitCtx, client, resourceGroup, name), + Timeout: timeout, } if _, err := stateConf.WaitForState(); err != nil { return fmt.Errorf("Error waiting for Template Deployment (%q in Resource Group %q) to be deleted: %+v", name, resourceGroup, err) diff --git a/azurerm/resource_arm_template_deployment_test.go b/azurerm/resource_arm_template_deployment_test.go index 6cc766177e72..99ce4d984116 100644 --- a/azurerm/resource_arm_template_deployment_test.go +++ b/azurerm/resource_arm_template_deployment_test.go @@ -5,6 +5,7 @@ import ( "net/http" "regexp" "testing" + "time" "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" @@ -12,6 +13,7 @@ import ( ) func TestAccAzureRMTemplateDeployment_basic(t *testing.T) { + resourceName := "azurerm_template_deployment.test" ri := acctest.RandInt() config := testAccAzureRMTemplateDeployment_basicMultiple(ri, testLocation()) resource.Test(t, resource.TestCase{ @@ -22,9 +24,37 @@ func TestAccAzureRMTemplateDeployment_basic(t *testing.T) { { Config: config, Check: resource.ComposeTestCheckFunc( - testCheckAzureRMTemplateDeploymentExists("azurerm_template_deployment.test"), + testCheckAzureRMTemplateDeploymentExists(resourceName), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMTemplateDeployment_requiresImport(t *testing.T) { + resourceName := "azurerm_template_deployment.test" + ri := acctest.RandInt() + location := testLocation() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMTemplateDeploymentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMTemplateDeployment_basicSingle(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMTemplateDeploymentExists(resourceName), + ), + }, + { + Config: testAccAzureRMTemplateDeployment_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_template_deployment"), + }, }, }) } @@ -184,7 +214,8 @@ func testCheckAzureRMTemplateDeploymentDisappears(name string) resource.TestChec return fmt.Errorf("Failed deleting Deployment %q (Resource Group %q): %+v", deploymentName, resourceGroup, err) } - return waitForTemplateDeploymentToBeDeleted(ctx, client, resourceGroup, deploymentName) + timeout := 40 * time.Minute + return waitForTemplateDeploymentToBeDeleted(ctx, client, resourceGroup, deploymentName, timeout) } } @@ -255,6 +286,21 @@ DEPLOY `, rInt, location, rInt, rInt) } +func testAccAzureRMTemplateDeployment_requiresImport(rInt int, location string) string { + template := testAccAzureRMTemplateDeployment_basicSingle(rInt, location) + + return fmt.Sprintf(` +%s + +resource "azurerm_template_deployment" "import" { + name = "${azurerm_template_deployment.test.name}" + resource_group_name = "${azurerm_template_deployment.test.resource_group_name}" + template_body = "${azurerm_template_deployment.test.template_body}" + deployment_mode = "${azurerm_template_deployment.test.deployment_mode}" +} +`, template) +} + func testAccAzureRMTemplateDeployment_basicMultiple(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { @@ -271,7 +317,7 @@ func testAccAzureRMTemplateDeployment_basicMultiple(rInt int, location string) s "contentVersion": "1.0.0.0", "parameters": { "storageAccountType": { - "type": "string", + "type": "String", "defaultValue": "Standard_LRS", "allowedValues": [ "Standard_LRS", @@ -354,8 +400,7 @@ resource "azurerm_key_vault" "test-kv" { access_policy { key_permissions = [] object_id = "${data.azurerm_client_config.current.service_principal_object_id}" - secret_permissions = [ - "get","list","set","purge"] + secret_permissions = ["get","list","set","purge"] tenant_id = "${data.azurerm_client_config.current.tenant_id}" } } @@ -393,7 +438,7 @@ resource "azurerm_template_deployment" "test" { "contentVersion": "1.0.0.0", "parameters": { "storageAccountType": { - "type": "string", + "type": "String", "defaultValue": "Standard_LRS", "allowedValues": [ "Standard_LRS", @@ -405,7 +450,7 @@ resource "azurerm_template_deployment" "test" { } }, "dnsLabelPrefix": { - "type": "string", + "type": "String", "metadata": { "description": "DNS Label for the Public IP. Must be lowercase. It should match with the following regular expression: ^[a-z][a-z0-9-]{1,61}[a-z0-9]$ or it will raise an error." } @@ -443,11 +488,11 @@ resource "azurerm_template_deployment" "test" { ], "outputs": { "testOutput": { - "type": "string", + "type": "String", "value": "Output Value" }, "accountName": { - "type": "string", + "type": "String", "value": "[variables('storageAccountName')]" } } @@ -489,7 +534,7 @@ func testAccAzureRMTemplateDeployment_withParams(rInt int, location string) stri "contentVersion": "1.0.0.0", "parameters": { "storageAccountType": { - "type": "string", + "type": "String", "defaultValue": "Standard_LRS", "allowedValues": [ "Standard_LRS", @@ -501,7 +546,7 @@ func testAccAzureRMTemplateDeployment_withParams(rInt int, location string) stri } }, "dnsLabelPrefix": { - "type": "string", + "type": "String", "metadata": { "description": "DNS Label for the Public IP. Must be lowercase. It should match with the following regular expression: ^[a-z][a-z0-9-]{1,61}[a-z0-9]$ or it will raise an error." } @@ -539,19 +584,19 @@ func testAccAzureRMTemplateDeployment_withParams(rInt int, location string) stri ], "outputs": { "testOutput": { - "type": "string", + "type": "String", "value": "Output Value" }, "accountName": { - "type": "string", + "type": "String", "value": "[variables('storageAccountName')]" } } } DEPLOY parameters { - dnsLabelPrefix = "terraform-test-%d" - storageAccountType = "Standard_GRS" + dnsLabelPrefix = "terraform-test-%d" + storageAccountType = "Standard_GRS" } deployment_mode = "Complete" } @@ -590,7 +635,7 @@ func testAccAzureRMTemplateDeployment_withOutputs(rInt int, location string) str "contentVersion": "1.0.0.0", "parameters": { "storageAccountType": { - "type": "string", + "type": "String", "defaultValue": "Standard_LRS", "allowedValues": [ "Standard_LRS", @@ -602,21 +647,21 @@ func testAccAzureRMTemplateDeployment_withOutputs(rInt int, location string) str } }, "dnsLabelPrefix": { - "type": "string", + "type": "String", "metadata": { "description": "DNS Label for the Public IP. Must be lowercase. It should match with the following regular expression: ^[a-z][a-z0-9-]{1,61}[a-z0-9]$ or it will raise an error." } }, "intParameter": { - "type": "int", + "type": "Int", "defaultValue": -123 }, "falseParameter": { - "type": "bool", + "type": "Bool", "defaultValue": false }, "trueParameter": { - "type": "bool", + "type": "Bool", "defaultValue": true } }, @@ -652,19 +697,19 @@ func testAccAzureRMTemplateDeployment_withOutputs(rInt int, location string) str ], "outputs": { "stringOutput": { - "type": "string", + "type": "String", "value": "[parameters('storageAccountType')]" }, "intOutput": { - "type": "int", + "type": "Int", "value": "[parameters('intParameter')]" }, "falseOutput": { - "type": "bool", + "type": "Bool", "value": "[parameters('falseParameter')]" }, "trueOutput": { - "type": "bool", + "type": "Bool", "value": "[parameters('trueParameter')]" } } @@ -700,7 +745,7 @@ resource "azurerm_resource_group" "test" { "contentVersion": "1.0.0.0", "parameters": { "storageAccountType": { - "type": "string", + "type": "String", "defaultValue": "Standard_LRS", "allowedValues": [ "Standard_LRS", @@ -730,7 +775,7 @@ resource "azurerm_resource_group" "test" { ], "outputs": { "testOutput": { - "type": "string", + "type": "String", "value": "Output Value" } } diff --git a/azurerm/resource_arm_traffic_manager_endpoint.go b/azurerm/resource_arm_traffic_manager_endpoint.go index df78a746eafb..c743d5598c02 100644 --- a/azurerm/resource_arm_traffic_manager_endpoint.go +++ b/azurerm/resource_arm_traffic_manager_endpoint.go @@ -1,13 +1,16 @@ package azurerm import ( + "context" "fmt" "log" "regexp" + "time" "github.com/Azure/azure-sdk-for-go/services/trafficmanager/mgmt/2017-05-01/trafficmanager" "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 resourceArmTrafficManagerEndpoint() *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": { @@ -114,23 +122,40 @@ func resourceArmTrafficManagerEndpoint() *schema.Resource { func resourceArmTrafficManagerEndpointCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).trafficManagerEndpointsClient + ctx := meta.(*ArmClient).StopContext log.Printf("[INFO] preparing arguments for TrafficManager Endpoint creation.") name := d.Get("name").(string) - endpointType := d.Get("type").(string) - fullEndpointType := fmt.Sprintf("Microsoft.Network/TrafficManagerProfiles/%s", endpointType) profileName := d.Get("profile_name").(string) + endpointType := d.Get("type").(string) resourceGroup := d.Get("resource_group_name").(string) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resourceGroup, profileName, endpointType, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Traffic Manager Endpoint %q (Endpoint Type %q / Profile %q / Resource Group %q): %+v", name, endpointType, profileName, resourceGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_traffic_manager_endpoint", *resp.ID) + } + } + + fullEndpointType := fmt.Sprintf("Microsoft.Network/TrafficManagerProfiles/%s", endpointType) + params := trafficmanager.Endpoint{ Name: &name, Type: &fullEndpointType, EndpointProperties: getArmTrafficManagerEndpointProperties(d), } - ctx := meta.(*ArmClient).StopContext - _, err := client.CreateOrUpdate(ctx, resourceGroup, profileName, endpointType, name, params) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, resourceGroup, profileName, endpointType, name, params) if err != nil { return err } @@ -200,19 +225,23 @@ func resourceArmTrafficManagerEndpointRead(d *schema.ResourceData, meta interfac func resourceArmTrafficManagerEndpointDelete(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).trafficManagerEndpointsClient + ctx := meta.(*ArmClient).StopContext id, err := parseAzureResourceID(d.Id()) if err != nil { return err } + + // TODO: fix the ID resGroup := id.ResourceGroup endpointType := d.Get("type").(string) profileName := id.Path["trafficManagerProfiles"] // endpoint name is keyed by endpoint type in ARM ID name := id.Path[endpointType] - ctx := meta.(*ArmClient).StopContext - resp, err := client.Delete(ctx, resGroup, profileName, endpointType, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + resp, err := client.Delete(waitCtx, resGroup, profileName, endpointType, name) if err != nil { if utils.ResponseWasNotFound(resp.Response) { return nil diff --git a/azurerm/resource_arm_traffic_manager_endpoint_test.go b/azurerm/resource_arm_traffic_manager_endpoint_test.go index 838b805a9615..ab80d71ae532 100644 --- a/azurerm/resource_arm_traffic_manager_endpoint_test.go +++ b/azurerm/resource_arm_traffic_manager_endpoint_test.go @@ -35,6 +35,30 @@ func TestAccAzureRMTrafficManagerEndpoint_basic(t *testing.T) { }) } +func TestAccAzureRMTrafficManagerEndpoint_requiresImport(t *testing.T) { + azureResourceName := "azurerm_traffic_manager_endpoint.testAzure" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMTrafficManagerEndpointDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMTrafficManagerEndpoint_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMTrafficManagerEndpointExists(azureResourceName), + ), + }, + { + Config: testAccAzureRMTrafficManagerEndpoint_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_traffic_manager_endpoint"), + }, + }, + }) +} + func TestAccAzureRMTrafficManagerEndpoint_disappears(t *testing.T) { azureResourceName := "azurerm_traffic_manager_endpoint.testAzure" externalResourceName := "azurerm_traffic_manager_endpoint.testExternal" @@ -390,6 +414,22 @@ resource "azurerm_traffic_manager_endpoint" "testExternal" { `, rInt, location, rInt, rInt, rInt, rInt, rInt, rInt) } +func testAccAzureRMTrafficManagerEndpoint_requiresImport(rInt int, location string) string { + template := testAccAzureRMTrafficManagerEndpoint_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_traffic_manager_endpoint" "import" { + name = "${azurerm_traffic_manager_endpoint.testExternal.name}" + type = "${azurerm_traffic_manager_endpoint.testExternal.type}" + target = "${azurerm_traffic_manager_endpoint.testExternal.target}" + weight = "${azurerm_traffic_manager_endpoint.testExternal.weight}" + profile_name = "${azurerm_traffic_manager_endpoint.testExternal.profile_name}" + resource_group_name = "${azurerm_traffic_manager_endpoint.testExternal.resource_group_name}" +} +`, template) +} + func testAccAzureRMTrafficManagerEndpoint_basicDisableExternal(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_traffic_manager_profile.go b/azurerm/resource_arm_traffic_manager_profile.go index 3f2632e3bf93..25dd115a9f43 100644 --- a/azurerm/resource_arm_traffic_manager_profile.go +++ b/azurerm/resource_arm_traffic_manager_profile.go @@ -2,26 +2,34 @@ package azurerm import ( "bytes" + "context" "fmt" "log" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/trafficmanager/mgmt/2017-05-01/trafficmanager" "github.com/hashicorp/terraform/helper/hashcode" "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" ) func resourceArmTrafficManagerProfile() *schema.Resource { return &schema.Resource{ - Create: resourceArmTrafficManagerProfileCreate, + Create: resourceArmTrafficManagerProfileCreateUpdate, Read: resourceArmTrafficManagerProfileRead, - Update: resourceArmTrafficManagerProfileCreate, + Update: resourceArmTrafficManagerProfileCreateUpdate, Delete: resourceArmTrafficManagerProfileDelete, 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": { @@ -114,15 +122,31 @@ func resourceArmTrafficManagerProfile() *schema.Resource { } } -func resourceArmTrafficManagerProfileCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmTrafficManagerProfileCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).trafficManagerProfilesClient + ctx := meta.(*ArmClient).StopContext log.Printf("[INFO] preparing arguments for Azure ARM virtual network creation.") name := d.Get("name").(string) + resGroup := d.Get("resource_group_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 Traffic Manager Profile %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_traffic_manager_profile", *resp.ID) + } + } + // must be provided in request location := "global" - resGroup := d.Get("resource_group_name").(string) tags := d.Get("tags").(map[string]interface{}) profile := trafficmanager.Profile{ @@ -132,8 +156,9 @@ func resourceArmTrafficManagerProfileCreate(d *schema.ResourceData, meta interfa Tags: expandTags(tags), } - ctx := meta.(*ArmClient).StopContext - _, err := client.CreateOrUpdate(ctx, resGroup, name, profile) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, resGroup, name, profile) if err != nil { return err } @@ -195,6 +220,7 @@ func resourceArmTrafficManagerProfileRead(d *schema.ResourceData, meta interface func resourceArmTrafficManagerProfileDelete(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).trafficManagerProfilesClient + ctx := meta.(*ArmClient).StopContext id, err := parseAzureResourceID(d.Id()) if err != nil { @@ -203,8 +229,9 @@ func resourceArmTrafficManagerProfileDelete(d *schema.ResourceData, meta interfa resGroup := id.ResourceGroup name := id.Path["trafficManagerProfiles"] - ctx := meta.(*ArmClient).StopContext - 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.Response) { return err diff --git a/azurerm/resource_arm_traffic_manager_profile_test.go b/azurerm/resource_arm_traffic_manager_profile_test.go index ea1576aa9989..df1e33d732b0 100644 --- a/azurerm/resource_arm_traffic_manager_profile_test.go +++ b/azurerm/resource_arm_traffic_manager_profile_test.go @@ -41,6 +41,30 @@ func TestAccAzureRMTrafficManagerProfile_geographic(t *testing.T) { }) } +func TestAccAzureRMTrafficManagerProfile_requiresImport(t *testing.T) { + resourceName := "azurerm_traffic_manager_profile.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMTrafficManagerProfileDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMTrafficManagerProfile_geographic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMTrafficManagerProfileExists(resourceName), + ), + }, + { + Config: testAccAzureRMTrafficManagerProfile_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_traffic_manager_profile"), + }, + }, + }) +} + func TestAccAzureRMTrafficManagerProfile_weighted(t *testing.T) { resourceName := "azurerm_traffic_manager_profile.test" ri := acctest.RandInt() @@ -335,6 +359,30 @@ resource "azurerm_traffic_manager_profile" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMTrafficManagerProfile_requiresImport(rInt int, location string) string { + template := testAccAzureRMTrafficManagerProfile_geographic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_traffic_manager_profile" "import" { + name = "${azurerm_traffic_manager_profile.test.name}" + resource_group_name = "${azurerm_traffic_manager_profile.test.resource_group_name}" + traffic_routing_method = "${azurerm_traffic_manager_profile.test.traffic_routing_method}" + + dns_config { + relative_name = "acctesttmp%d" + ttl = 30 + } + + monitor_config { + protocol = "https" + port = 443 + path = "/" + } +} +`, template, rInt) +} + func testAccAzureRMTrafficManagerProfile_weighted(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_user_assigned_identity.go b/azurerm/resource_arm_user_assigned_identity.go index 768086bbb72f..eb837d7fab17 100644 --- a/azurerm/resource_arm_user_assigned_identity.go +++ b/azurerm/resource_arm_user_assigned_identity.go @@ -1,12 +1,15 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi" "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" ) @@ -19,6 +22,11 @@ func resourceArmUserAssignedIdentity() *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": { @@ -49,8 +57,23 @@ func resourceArmUserAssignedIdentityCreateUpdate(d *schema.ResourceData, meta in log.Printf("[INFO] preparing arguments for Azure ARM user identity creation.") name := d.Get("name").(string) - location := d.Get("location").(string) resGroup := d.Get("resource_group_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 User Assigned Identity %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_user_assigned_identity", *resp.ID) + } + } + + location := d.Get("location").(string) tags := d.Get("tags").(map[string]interface{}) identity := msi.Identity{ Name: &name, @@ -58,7 +81,9 @@ func resourceArmUserAssignedIdentityCreateUpdate(d *schema.ResourceData, meta in Tags: expandTags(tags), } - _, err := client.CreateOrUpdate(ctx, resGroup, name, identity) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + _, err := client.CreateOrUpdate(waitCtx, resGroup, name, identity) if err != nil { return fmt.Errorf("Error Creating/Updating User Assigned Identity %q (Resource Group %q): %+v", name, resGroup, err) } @@ -124,7 +149,9 @@ func resourceArmUserAssignedIdentityDelete(d *schema.ResourceData, meta interfac resGroup := id.ResourceGroup name := id.Path["userAssignedIdentities"] - _, err = client.Delete(ctx, resGroup, name) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + _, err = client.Delete(waitCtx, resGroup, name) if err != nil { return fmt.Errorf("Error deleting User Assigned Identity %q (Resource Group %q): %+v", name, resGroup, err) } diff --git a/azurerm/resource_arm_user_assigned_identity_test.go b/azurerm/resource_arm_user_assigned_identity_test.go index 6dc61cc0525d..aacb947cdbd9 100644 --- a/azurerm/resource_arm_user_assigned_identity_test.go +++ b/azurerm/resource_arm_user_assigned_identity_test.go @@ -34,6 +34,31 @@ func TestAccAzureRMUserAssignedIdentity_basic(t *testing.T) { }) } +func TestAccAzureRMUserAssignedIdentity_requiresImport(t *testing.T) { + resourceName := "azurerm_user_assigned_identity.test" + ri := acctest.RandInt() + rs := acctest.RandString(14) + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMUserAssignedIdentityDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMUserAssignedIdentity_basic(ri, location, rs), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMUserAssignedIdentityExists(resourceName), + ), + }, + { + Config: testAccAzureRMUserAssignedIdentity_requiresImport(ri, location, rs), + ExpectError: testRequiresImportError("azurerm_user_assigned_identity"), + }, + }, + }) +} + func testCheckAzureRMUserAssignedIdentityExists(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { // Ensure we have enough information in state to look up in API @@ -106,3 +131,16 @@ resource "azurerm_user_assigned_identity" "test" { } `, rInt, location, rString) } + +func testAccAzureRMUserAssignedIdentity_requiresImport(rInt int, location string, rString string) string { + template := testAccAzureRMUserAssignedIdentity_basic(rInt, location, rString) + return fmt.Sprintf( + ` +%s + +resource "azurerm_user_assigned_identity" "import" { + name = "${azurerm_user_assigned_identity.test.name}" + resource_group_name = "${azurerm_user_assigned_identity.test.resource_group_name}" + location = "${azurerm_user_assigned_identity.test.location}" +}`, template) +} diff --git a/azurerm/resource_arm_virtual_machine.go b/azurerm/resource_arm_virtual_machine.go index 88adab6971be..d8f262a6deab 100644 --- a/azurerm/resource_arm_virtual_machine.go +++ b/azurerm/resource_arm_virtual_machine.go @@ -4,8 +4,8 @@ import ( "bytes" "fmt" "log" - "net/url" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-06-01/compute" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform/helper/hashcode" "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" "golang.org/x/net/context" ) @@ -21,13 +22,18 @@ var virtualMachineResourceName = "azurerm_virtual_machine" func resourceArmVirtualMachine() *schema.Resource { return &schema.Resource{ - Create: resourceArmVirtualMachineCreate, + Create: resourceArmVirtualMachineCreateUpdate, Read: resourceArmVirtualMachineRead, - Update: resourceArmVirtualMachineCreate, + Update: resourceArmVirtualMachineCreateUpdate, Delete: resourceArmVirtualMachineDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 60), + Update: schema.DefaultTimeout(time.Minute * 60), + Delete: schema.DefaultTimeout(time.Minute * 60), + }, Schema: map[string]*schema.Schema{ "name": { @@ -551,15 +557,30 @@ func resourceArmVirtualMachine() *schema.Resource { } } -func resourceArmVirtualMachineCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmVirtualMachineCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).vmClient ctx := meta.(*ArmClient).StopContext log.Printf("[INFO] preparing arguments for Azure ARM Virtual Machine creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_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 Virtual Machine %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_virtual_machine", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) tags := d.Get("tags").(map[string]interface{}) expandedTags := expandTags(tags) zones := expandZones(d.Get("zones").([]interface{})) @@ -657,7 +678,9 @@ func resourceArmVirtualMachineCreate(d *schema.ResourceData, meta interface{}) e return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -835,7 +858,9 @@ func resourceArmVirtualMachineDelete(d *schema.ResourceData, meta interface{}) e return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -850,11 +875,11 @@ func resourceArmVirtualMachineDelete(d *schema.ResourceData, meta interface{}) e } if osDisk.Vhd != nil { - if err = resourceArmVirtualMachineDeleteVhd(*osDisk.Vhd.URI, meta); err != nil { + if err = resourceArmVirtualMachineDeleteVhd(waitCtx, *osDisk.Vhd.URI, meta); err != nil { return fmt.Errorf("Error deleting OS Disk VHD: %+v", err) } } else if osDisk.ManagedDisk != nil { - if err = resourceArmVirtualMachineDeleteManagedDisk(*osDisk.ManagedDisk.ID, meta); err != nil { + if err = resourceArmVirtualMachineDeleteManagedDisk(waitCtx, *osDisk.ManagedDisk.ID, meta); err != nil { return fmt.Errorf("Error deleting OS Managed Disk: %+v", err) } } else { @@ -873,11 +898,11 @@ func resourceArmVirtualMachineDelete(d *schema.ResourceData, meta interface{}) e for _, disk := range disks { if disk.Vhd != nil { - if err = resourceArmVirtualMachineDeleteVhd(*disk.Vhd.URI, meta); err != nil { + if err = resourceArmVirtualMachineDeleteVhd(waitCtx, *disk.Vhd.URI, meta); err != nil { return fmt.Errorf("Error deleting Data Disk VHD: %+v", err) } } else if disk.ManagedDisk != nil { - if err = resourceArmVirtualMachineDeleteManagedDisk(*disk.ManagedDisk.ID, meta); err != nil { + if err = resourceArmVirtualMachineDeleteManagedDisk(waitCtx, *disk.ManagedDisk.ID, meta); err != nil { return fmt.Errorf("Error deleting Data Managed Disk: %+v", err) } } else { @@ -889,45 +914,31 @@ func resourceArmVirtualMachineDelete(d *schema.ResourceData, meta interface{}) e return nil } -func resourceArmVirtualMachineDeleteVhd(uri string, meta interface{}) error { +func resourceArmVirtualMachineDeleteVhd(ctx context.Context, uri string, meta interface{}) error { armClient := meta.(*ArmClient) - ctx := armClient.StopContext environment := armClient.environment - vhdURL, err := url.Parse(uri) - if err != nil { - return fmt.Errorf("Cannot parse Disk VHD URI: %s", err) - } - - blobDomainSuffix := environment.StorageEndpointSuffix - if !strings.HasSuffix(strings.ToLower(vhdURL.Host), strings.ToLower(blobDomainSuffix)) { - return fmt.Errorf("Error: Disk VHD URI %q doesn't appear to be a Blob Storage URI (%q) - expected a suffix of %q)", uri, vhdURL.Host, blobDomainSuffix) - } - // VHD URI is in the form: https://storageAccountName.blob.core.windows.net/containerName/blobName - storageAccountName := strings.Split(vhdURL.Host, ".")[0] - path := strings.Split(strings.TrimPrefix(vhdURL.Path, "/"), "/") - containerName := path[0] - blobName := path[1] + blobId, err := parseStorageBlobID(uri, environment) - resourceGroupName, err := findStorageAccountResourceGroup(meta, storageAccountName) + resourceGroupName, err := findStorageAccountResourceGroup(meta, blobId.storageAccountName) if err != nil { - return fmt.Errorf("Error finding resource group for storage account %s: %+v", storageAccountName, err) + return fmt.Errorf("Error finding resource group for storage account %s: %+v", blobId.storageAccountName, err) } - blobClient, saExists, err := armClient.getBlobStorageClientForStorageAccount(ctx, resourceGroupName, storageAccountName) + blobClient, saExists, err := armClient.getBlobStorageClientForStorageAccount(ctx, resourceGroupName, blobId.storageAccountName) if err != nil { return fmt.Errorf("Error creating blob store client for VHD deletion: %+v", err) } if !saExists { - log.Printf("[INFO] Storage Account %q in resource group %q doesn't exist so the VHD blob won't exist", storageAccountName, resourceGroupName) + log.Printf("[INFO] Storage Account %q in resource group %q doesn't exist so the VHD blob won't exist", blobId.storageAccountName, resourceGroupName) return nil } - log.Printf("[INFO] Deleting VHD blob %s", blobName) - container := blobClient.GetContainerReference(containerName) - blob := container.GetBlobReference(blobName) + log.Printf("[INFO] Deleting VHD blob %s", blobId.blobName) + container := blobClient.GetContainerReference(blobId.containerName) + blob := container.GetBlobReference(blobId.blobName) options := &storage.DeleteBlobOptions{} err = blob.Delete(options) if err != nil { @@ -937,9 +948,8 @@ func resourceArmVirtualMachineDeleteVhd(uri string, meta interface{}) error { return nil } -func resourceArmVirtualMachineDeleteManagedDisk(managedDiskID string, meta interface{}) error { +func resourceArmVirtualMachineDeleteManagedDisk(ctx context.Context, managedDiskID string, meta interface{}) error { client := meta.(*ArmClient).diskClient - ctx := meta.(*ArmClient).StopContext id, err := parseAzureResourceID(managedDiskID) if err != nil { @@ -963,6 +973,7 @@ func resourceArmVirtualMachineDeleteManagedDisk(managedDiskID string, meta inter func flattenAzureRmVirtualMachinePlan(plan *compute.Plan) []interface{} { result := make(map[string]interface{}) + result["name"] = *plan.Name result["publisher"] = *plan.Publisher result["product"] = *plan.Product diff --git a/azurerm/resource_arm_virtual_machine_data_disk_attachment.go b/azurerm/resource_arm_virtual_machine_data_disk_attachment.go index 49ba55405015..227fb1fab139 100644 --- a/azurerm/resource_arm_virtual_machine_data_disk_attachment.go +++ b/azurerm/resource_arm_virtual_machine_data_disk_attachment.go @@ -1,13 +1,16 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-06-01/compute" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -20,6 +23,11 @@ func resourceArmVirtualMachineDataDiskAttachment() *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{ "managed_disk_id": { @@ -130,18 +138,24 @@ func resourceArmVirtualMachineDataDiskAttachmentCreateUpdate(d *schema.ResourceD } disks := *virtualMachine.StorageProfile.DataDisks + + // iterate over the disks and swap it out in-place + existingIndex := -1 + for i, disk := range disks { + if *disk.Name == name { + existingIndex = i + break + } + } + + id := fmt.Sprintf("%s/dataDisks/%s", virtualMachineId, name) if d.IsNewResource() { - disks = append(disks, expandedDisk) - } else { - // iterate over the disks and swap it out in-place - existingIndex := -1 - for i, disk := range disks { - if *disk.Name == name { - existingIndex = i - break - } + if existingIndex >= 0 { + return tf.ImportAsExistsError("azurerm_virtual_machine_data_disk_attachment", id) } + disks = append(disks, expandedDisk) + } else { if existingIndex == -1 { return fmt.Errorf("Unable to find Disk %q attached to Virtual Machine %q (Resource Group %q)", name, virtualMachineName, resourceGroup) } @@ -159,13 +173,14 @@ func resourceArmVirtualMachineDataDiskAttachmentCreateUpdate(d *schema.ResourceD return fmt.Errorf("Error updating Virtual Machine %q (Resource Group %q) with Disk %q: %+v", virtualMachineName, resourceGroup, name, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for Virtual Machine %q (Resource Group %q) to finish updating Disk %q: %+v", virtualMachineName, resourceGroup, name, err) } - d.SetId(fmt.Sprintf("%s/dataDisks/%s", virtualMachineId, name)) - + d.SetId(id) return resourceArmVirtualMachineDataDiskAttachmentRead(d, meta) } @@ -266,7 +281,9 @@ func resourceArmVirtualMachineDataDiskAttachmentDelete(d *schema.ResourceData, m return fmt.Errorf("Error removing Disk %q from Virtual Machine %q (Resource Group %q): %+v", name, virtualMachineName, resourceGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for Disk %q to be removed from Virtual Machine %q (Resource Group %q): %+v", name, virtualMachineName, resourceGroup, err) } diff --git a/azurerm/resource_arm_virtual_machine_data_disk_attachment_test.go b/azurerm/resource_arm_virtual_machine_data_disk_attachment_test.go index 5cfcb8cd44c3..5bcd549901ff 100644 --- a/azurerm/resource_arm_virtual_machine_data_disk_attachment_test.go +++ b/azurerm/resource_arm_virtual_machine_data_disk_attachment_test.go @@ -35,6 +35,29 @@ func TestAccAzureRMVirtualMachineDataDiskAttachment_basic(t *testing.T) { }) } +func TestAccAzureRMVirtualMachineDataDiskAttachment_requiresImport(t *testing.T) { + resourceName := "azurerm_virtual_machine_data_disk_attachment.test" + ri := acctest.RandInt() + location := testLocation() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualMachineDataDiskAttachmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMVirtualMachineDataDiskAttachment_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualMachineDataDiskAttachmentExists(resourceName), + ), + }, + { + Config: testAccAzureRMVirtualMachineDataDiskAttachment_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_virtual_machine_data_disk_attachment"), + }, + }, + }) +} + func TestAccAzureRMVirtualMachineDataDiskAttachment_multipleDisks(t *testing.T) { firstResourceName := "azurerm_virtual_machine_data_disk_attachment.first" secondResourceName := "azurerm_virtual_machine_data_disk_attachment.second" @@ -242,6 +265,20 @@ resource "azurerm_virtual_machine_data_disk_attachment" "test" { `, template) } +func testAccAzureRMVirtualMachineDataDiskAttachment_requiresImport(rInt int, location string) string { + template := testAccAzureRMVirtualMachineDataDiskAttachment_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_machine_data_disk_attachment" "import" { + managed_disk_id = "${azurerm_virtual_machine_data_disk_attachment.test.managed_disk_id}" + virtual_machine_id = "${azurerm_virtual_machine_data_disk_attachment.test.virtual_machine_id}" + lun = "${azurerm_virtual_machine_data_disk_attachment.test.lun}" + caching = "${azurerm_virtual_machine_data_disk_attachment.test.caching}" +} +`, template) +} + func testAccAzureRMVirtualMachineDataDiskAttachment_multipleDisks(rInt int, location string) string { template := testAccAzureRMVirtualMachineDataDiskAttachment_template(rInt, location) return fmt.Sprintf(` diff --git a/azurerm/resource_arm_virtual_machine_extension.go b/azurerm/resource_arm_virtual_machine_extension.go index 232b999fa24d..1b74db999086 100644 --- a/azurerm/resource_arm_virtual_machine_extension.go +++ b/azurerm/resource_arm_virtual_machine_extension.go @@ -1,24 +1,32 @@ package azurerm import ( + "context" "fmt" + "time" "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-06-01/compute" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/structure" "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" ) func resourceArmVirtualMachineExtensions() *schema.Resource { return &schema.Resource{ - Create: resourceArmVirtualMachineExtensionsCreate, + Create: resourceArmVirtualMachineExtensionsCreateUpdate, Read: resourceArmVirtualMachineExtensionsRead, - Update: resourceArmVirtualMachineExtensionsCreate, + Update: resourceArmVirtualMachineExtensionsCreateUpdate, Delete: resourceArmVirtualMachineExtensionsDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 60), + Update: schema.DefaultTimeout(time.Minute * 60), + Delete: schema.DefaultTimeout(time.Minute * 60), + }, Schema: map[string]*schema.Schema{ "name": { @@ -78,14 +86,29 @@ func resourceArmVirtualMachineExtensions() *schema.Resource { } } -func resourceArmVirtualMachineExtensionsCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmVirtualMachineExtensionsCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).vmExtensionClient ctx := meta.(*ArmClient).StopContext name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) vmName := d.Get("virtual_machine_name").(string) resGroup := d.Get("resource_group_name").(string) + + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, vmName, name, "") + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of VM Extension %q (Virtual Machine %q / Resource Group %q): %+v", name, vmName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_virtual_machine_extension", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) publisher := d.Get("publisher").(string) extensionType := d.Get("type").(string) typeHandlerVersion := d.Get("type_handler_version").(string) @@ -124,7 +147,9 @@ func resourceArmVirtualMachineExtensionsCreate(d *schema.ResourceData, meta inte return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } @@ -210,7 +235,9 @@ func resourceArmVirtualMachineExtensionsDelete(d *schema.ResourceData, meta inte return err } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return err } diff --git a/azurerm/resource_arm_virtual_machine_extension_test.go b/azurerm/resource_arm_virtual_machine_extension_test.go index 6e813a717d68..35e1fa4e3432 100644 --- a/azurerm/resource_arm_virtual_machine_extension_test.go +++ b/azurerm/resource_arm_virtual_machine_extension_test.go @@ -41,6 +41,30 @@ func TestAccAzureRMVirtualMachineExtension_basic(t *testing.T) { }) } +func TestAccAzureRMVirtualMachineExtension_requiresImport(t *testing.T) { + resourceName := "azurerm_virtual_machine_extension.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualMachineExtensionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMVirtualMachineExtension_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualMachineExtensionExists(resourceName), + ), + }, + { + Config: testAccAzureRMVirtualMachineExtension_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_virtual_machine_extension"), + }, + }, + }) +} + func TestAccAzureRMVirtualMachineExtension_concurrent(t *testing.T) { firstResourceName := "azurerm_virtual_machine_extension.test" secondResourceName := "azurerm_virtual_machine_extension.test2" @@ -245,6 +269,28 @@ SETTINGS `, rInt, location, rInt, rInt, rInt, rInt, rInt, rInt, rInt) } +func testAccAzureRMVirtualMachineExtension_requiresImport(rInt int, location string) string { + template := testAccAzureRMVirtualMachineExtension_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_machine_extension" "import" { + name = "${azurerm_virtual_machine_extension.test.name}" + location = "${azurerm_virtual_machine_extension.test.location}" + resource_group_name = "${azurerm_virtual_machine_extension.test.resource_group_name}" + virtual_machine_name = "${azurerm_virtual_machine_extension.test.virtual_machine_name}" + publisher = "${azurerm_virtual_machine_extension.test.publisher}" + type = "${azurerm_virtual_machine_extension.test.type}" + type_handler_version = "${azurerm_virtual_machine_extension.test.type_handler_version}" + settings = "${azurerm_virtual_machine_extension.test.settings}" + + tags { + environment = "Production" + } +} +`, template) +} + func testAccAzureRMVirtualMachineExtension_basicUpdate(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_virtual_machine_scale_set.go b/azurerm/resource_arm_virtual_machine_scale_set.go index bcd153f3226b..c243a2cf1f60 100644 --- a/azurerm/resource_arm_virtual_machine_scale_set.go +++ b/azurerm/resource_arm_virtual_machine_scale_set.go @@ -2,29 +2,36 @@ package azurerm import ( "bytes" + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-06-01/compute" - "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/structure" "github.com/hashicorp/terraform/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/utils" ) func resourceArmVirtualMachineScaleSet() *schema.Resource { return &schema.Resource{ - Create: resourceArmVirtualMachineScaleSetCreate, + Create: resourceArmVirtualMachineScaleSetCreateUpdate, Read: resourceArmVirtualMachineScaleSetRead, - Update: resourceArmVirtualMachineScaleSetCreate, + Update: resourceArmVirtualMachineScaleSetCreateUpdate, Delete: resourceArmVirtualMachineScaleSetDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(time.Minute * 60), + Update: schema.DefaultTimeout(time.Minute * 60), + Delete: schema.DefaultTimeout(time.Minute * 60), + }, Schema: map[string]*schema.Schema{ "name": { @@ -661,15 +668,30 @@ func resourceArmVirtualMachineScaleSet() *schema.Resource { } } -func resourceArmVirtualMachineScaleSetCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmVirtualMachineScaleSetCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).vmScaleSetClient ctx := meta.(*ArmClient).StopContext log.Printf("[INFO] preparing arguments for Azure ARM Virtual Machine Scale Set creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_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 Virtual Machine Scale Set %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_virtual_machine_scale_set", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) tags := d.Get("tags").(map[string]interface{}) zones := expandZones(d.Get("zones").([]interface{})) @@ -767,7 +789,9 @@ func resourceArmVirtualMachineScaleSetCreate(d *schema.ResourceData, meta interf return err } - if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + if err := future.WaitForCompletionRef(waitCtx, client.Client); err != nil { return err } @@ -942,7 +966,9 @@ func resourceArmVirtualMachineScaleSetDelete(d *schema.ResourceData, meta interf return err } - if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + if err := future.WaitForCompletionRef(waitCtx, client.Client); err != nil { return err } diff --git a/azurerm/resource_arm_virtual_machine_scale_set_test.go b/azurerm/resource_arm_virtual_machine_scale_set_test.go index ef5092302b93..6b5a47858178 100644 --- a/azurerm/resource_arm_virtual_machine_scale_set_test.go +++ b/azurerm/resource_arm_virtual_machine_scale_set_test.go @@ -64,6 +64,29 @@ func TestAccAzureRMVirtualMachineScaleSet_standardSSD(t *testing.T) { }) } +func TestAccAzureRMVirtualMachineScaleSet_requiresImport(t *testing.T) { + resourceName := "azurerm_virtual_machine_scale_set.test" + ri := acctest.RandInt() + location := testLocation() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualMachineScaleSetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMVirtualMachineScaleSet_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualMachineScaleSetExists(resourceName), + ), + }, + { + Config: testAccAzureRMVirtualMachineScaleSet_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_virtual_machine_scale_set"), + }, + }, + }) +} + func TestAccAzureRMVirtualMachineScaleSet_basicPublicIP(t *testing.T) { resourceName := "azurerm_virtual_machine_scale_set.test" ri := acctest.RandInt() @@ -1270,6 +1293,56 @@ resource "azurerm_virtual_machine_scale_set" "test" { `, rInt, location) } +func testAccAzureRMVirtualMachineScaleSet_requiresImport(rInt int, location string) string { + template := testAccAzureRMVirtualMachineScaleSet_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_machine_scale_set" "import" { + name = "${azurerm_virtual_machine_scale_set.test.name}" + location = "${azurerm_virtual_machine_scale_set.test.location}" + resource_group_name = "${azurerm_virtual_machine_scale_set.test.resource_group_name}" + upgrade_policy_mode = "${azurerm_virtual_machine_scale_set.test.upgrade_policy_mode}" + + sku { + name = "Standard_D1_v2" + tier = "Standard" + capacity = 2 + } + + os_profile { + computer_name_prefix = "testvm-%d" + admin_username = "myadmin" + admin_password = "Passwword1234" + } + + network_profile { + name = "TestNetworkProfile-%d" + primary = true + + ip_configuration { + name = "TestIPConfiguration" + subnet_id = "${azurerm_subnet.test.id}" + } + } + + storage_profile_os_disk { + name = "osDiskProfile" + caching = "ReadWrite" + create_option = "FromImage" + vhd_containers = ["${azurerm_storage_account.test.primary_blob_endpoint}${azurerm_storage_container.test.name}"] + } + + storage_profile_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "16.04-LTS" + version = "latest" + } +} +`, template, rInt, rInt) +} + func testAccAzureRMVirtualMachineScaleSet_basicPublicIP(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_virtual_machine_test.go b/azurerm/resource_arm_virtual_machine_test.go index 28754ade9fad..63fdd0d97c05 100644 --- a/azurerm/resource_arm_virtual_machine_test.go +++ b/azurerm/resource_arm_virtual_machine_test.go @@ -12,6 +12,30 @@ import ( "github.com/hashicorp/terraform/terraform" ) +func TestAccAzureRMVirtualMachine_requiresImport(t *testing.T) { + resourceName := "azurerm_virtual_machine.test" + var vm compute.VirtualMachine + ri := acctest.RandInt() + location := testLocation() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualMachineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMVirtualMachine_basicLinuxMachine(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualMachineExists(resourceName, &vm), + ), + }, + { + Config: testAccAzureRMVirtualMachine_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_virtual_machine"), + }, + }, + }) +} + func TestAccAzureRMVirtualMachine_winTimeZone(t *testing.T) { resourceName := "azurerm_virtual_machine.test" var vm compute.VirtualMachine diff --git a/azurerm/resource_arm_virtual_machine_unmanaged_disks_test.go b/azurerm/resource_arm_virtual_machine_unmanaged_disks_test.go index 1e35dc09e9ab..ba99805e9182 100644 --- a/azurerm/resource_arm_virtual_machine_unmanaged_disks_test.go +++ b/azurerm/resource_arm_virtual_machine_unmanaged_disks_test.go @@ -684,6 +684,51 @@ resource "azurerm_virtual_machine" "test" { `, rInt, location, rInt, rInt, rInt, rInt, rInt, rInt) } +func testAccAzureRMVirtualMachine_requiresImport(rInt int, location string) string { + template := testAccAzureRMVirtualMachine_basicLinuxMachine(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_machine" "import" { + name = "${azurerm_virtual_machine.test.name}" + location = "${azurerm_virtual_machine.test.location}" + resource_group_name = "${azurerm_virtual_machine.test.resource_group_name}" + vm_size = "${azurerm_virtual_machine.test.vm_size}" + network_interface_ids = ["${azurerm_network_interface.test.id}"] + + storage_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "16.04-LTS" + version = "latest" + } + + storage_os_disk { + name = "myosdisk1" + vhd_uri = "${azurerm_storage_account.test.primary_blob_endpoint}${azurerm_storage_container.test.name}/myosdisk1.vhd" + caching = "ReadWrite" + create_option = "FromImage" + disk_size_gb = 45 + } + + os_profile { + computer_name = "hn%d" + admin_username = "testadmin" + admin_password = "Password1234!" + } + + os_profile_linux_config { + disable_password_authentication = false + } + + tags { + environment = "Production" + cost-center = "Ops" + } +} +`, template, rInt) +} + func testAccAzureRMVirtualMachine_basicLinuxMachine_destroyVM(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_virtual_network.go b/azurerm/resource_arm_virtual_network.go index bd180bc5cbc4..3a8164eaec37 100644 --- a/azurerm/resource_arm_virtual_network.go +++ b/azurerm/resource_arm_virtual_network.go @@ -6,10 +6,12 @@ import ( "fmt" "log" "net/http" + "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" "github.com/hashicorp/terraform/helper/hashcode" "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,13 +19,18 @@ var virtualNetworkResourceName = "azurerm_virtual_network" func resourceArmVirtualNetwork() *schema.Resource { return &schema.Resource{ - Create: resourceArmVirtualNetworkCreate, + Create: resourceArmVirtualNetworkCreateUpdate, Read: resourceArmVirtualNetworkRead, - Update: resourceArmVirtualNetworkCreate, + Update: resourceArmVirtualNetworkCreateUpdate, Delete: resourceArmVirtualNetworkDelete, 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": { @@ -80,15 +87,30 @@ func resourceArmVirtualNetwork() *schema.Resource { } } -func resourceArmVirtualNetworkCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmVirtualNetworkCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).vnetClient ctx := meta.(*ArmClient).StopContext log.Printf("[INFO] preparing arguments for Azure ARM virtual network creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_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 Virtual Network %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_virtual_network", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) tags := d.Get("tags").(map[string]interface{}) vnetProperties, vnetPropsErr := expandVirtualNetworkProperties(ctx, d, meta) if vnetPropsErr != nil { @@ -124,7 +146,9 @@ func resourceArmVirtualNetworkCreate(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("Error Creating/Updating Virtual Network %q (Resource Group %q): %+v", name, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for completion of Virtual Network %q (Resource Group %q): %+v", name, resGroup, err) } @@ -214,7 +238,9 @@ func resourceArmVirtualNetworkDelete(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("Error deleting Virtual Network %q (Resource Group %q): %+v", name, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for deletion of Virtual Network %q (Resource Group %q): %+v", name, resGroup, err) } diff --git a/azurerm/resource_arm_virtual_network_gateway.go b/azurerm/resource_arm_virtual_network_gateway.go index 083895abaa0d..4d7f38672822 100644 --- a/azurerm/resource_arm_virtual_network_gateway.go +++ b/azurerm/resource_arm_virtual_network_gateway.go @@ -2,9 +2,11 @@ package azurerm import ( "bytes" + "context" "fmt" "log" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" "github.com/hashicorp/terraform/helper/hashcode" @@ -12,6 +14,7 @@ import ( "github.com/hashicorp/terraform/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/utils" ) @@ -25,8 +28,12 @@ func resourceArmVirtualNetworkGateway() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, - CustomizeDiff: resourceArmVirtualNetworkGatewayCustomizeDiff, + 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": { @@ -269,8 +276,23 @@ func resourceArmVirtualNetworkGatewayCreateUpdate(d *schema.ResourceData, meta i log.Printf("[INFO] preparing arguments for AzureRM Virtual Network Gateway creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_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 Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_virtual_network_gateway", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) tags := d.Get("tags").(map[string]interface{}) properties, err := getArmVirtualNetworkGatewayProperties(d) @@ -290,7 +312,9 @@ func resourceArmVirtualNetworkGatewayCreateUpdate(d *schema.ResourceData, meta i return fmt.Errorf("Error Creating/Updating AzureRM Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) } - if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + if err := future.WaitForCompletionRef(waitCtx, client.Client); err != nil { return fmt.Errorf("Error waiting for completion of AzureRM Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) } @@ -384,7 +408,9 @@ func resourceArmVirtualNetworkGatewayDelete(d *schema.ResourceData, meta interfa return fmt.Errorf("Error deleting Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) } - if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + if err := future.WaitForCompletionRef(waitCtx, client.Client); err != nil { return fmt.Errorf("Error waiting for deletion of Virtual Network Gateway %q (Resource Group %q): %+v", name, resGroup, err) } diff --git a/azurerm/resource_arm_virtual_network_gateway_connection.go b/azurerm/resource_arm_virtual_network_gateway_connection.go index 663ab380d3a4..a0e799c48248 100644 --- a/azurerm/resource_arm_virtual_network_gateway_connection.go +++ b/azurerm/resource_arm_virtual_network_gateway_connection.go @@ -1,14 +1,17 @@ package azurerm import ( + "context" "fmt" "log" + "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/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/utils" ) @@ -22,6 +25,11 @@ func resourceArmVirtualNetworkGatewayConnection() *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": { @@ -230,8 +238,23 @@ func resourceArmVirtualNetworkGatewayConnectionCreateUpdate(d *schema.ResourceDa log.Printf("[INFO] preparing arguments for AzureRM Virtual Network Gateway Connection creation.") name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) resGroup := d.Get("resource_group_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 Virtual Network Gateway Connection %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_virtual_network_gateway_connection", *resp.ID) + } + } + + location := azureRMNormalizeLocation(d.Get("location").(string)) tags := d.Get("tags").(map[string]interface{}) properties, err := getArmVirtualNetworkGatewayConnectionProperties(d) @@ -251,7 +274,10 @@ func resourceArmVirtualNetworkGatewayConnectionCreateUpdate(d *schema.ResourceDa return fmt.Errorf("Error Creating/Updating AzureRM Virtual Network Gateway Connection %q (Resource Group %q): %+v", name, resGroup, err) } - if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) + if err != nil { return fmt.Errorf("Error waiting for completion of Virtual Network Gateway Connection %q (Resource Group %q): %+v", name, resGroup, err) } @@ -361,7 +387,10 @@ func resourceArmVirtualNetworkGatewayConnectionDelete(d *schema.ResourceData, me return fmt.Errorf("Error Deleting Virtual Network Gateway Connection %q (Resource Group %q): %+v", name, resGroup, err) } - if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) + if err != nil { return fmt.Errorf("Error waiting for deletion of Virtual Network Gateway Connection %q (Resource Group %q): %+v", name, resGroup, err) } diff --git a/azurerm/resource_arm_virtual_network_gateway_connection_test.go b/azurerm/resource_arm_virtual_network_gateway_connection_test.go index d58ac0307e19..59b547d8eb4a 100644 --- a/azurerm/resource_arm_virtual_network_gateway_connection_test.go +++ b/azurerm/resource_arm_virtual_network_gateway_connection_test.go @@ -35,6 +35,29 @@ func TestAccAzureRMVirtualNetworkGatewayConnection_sitetosite(t *testing.T) { }) } +func TestAccAzureRMVirtualNetworkGatewayConnection_requiresImport(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualNetworkGatewayConnectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMVirtualNetworkGatewayConnection_sitetosite(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualNetworkGatewayConnectionExists("azurerm_virtual_network_gateway_connection.test"), + ), + }, + { + Config: testAccAzureRMVirtualNetworkGatewayConnection_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_virtual_network_gateway_connection"), + }, + }, + }) +} + func TestAccAzureRMVirtualNetworkGatewayConnection_vnettonet(t *testing.T) { firstResourceName := "azurerm_virtual_network_gateway_connection.test_1" secondResourceName := "azurerm_virtual_network_gateway_connection.test_2" @@ -241,6 +264,23 @@ resource "azurerm_virtual_network_gateway_connection" "test" { `, rInt, location) } +func testAccAzureRMVirtualNetworkGatewayConnection_requiresImport(rInt int, location string) string { + template := testAccAzureRMVirtualNetworkGatewayConnection_sitetosite(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_network_gateway_connection" "import" { + name = "${azurerm_virtual_network_gateway_connection.test.name}" + location = "${azurerm_virtual_network_gateway_connection.test.location}" + resource_group_name = "${azurerm_virtual_network_gateway_connection.test.resource_group_name}" + type = "${azurerm_virtual_network_gateway_connection.test.type}" + virtual_network_gateway_id = "${azurerm_virtual_network_gateway_connection.test.virtual_network_gateway_id}" + local_network_gateway_id = "${azurerm_virtual_network_gateway_connection.test.local_network_gateway_id}" + shared_key = "${azurerm_virtual_network_gateway_connection.test.shared_key}" +} +`, template) +} + func testAccAzureRMVirtualNetworkGatewayConnection_vnettovnet(rInt, rInt2 int, sharedKey, location, altLocation string) string { return fmt.Sprintf(` variable "random1" { diff --git a/azurerm/resource_arm_virtual_network_gateway_test.go b/azurerm/resource_arm_virtual_network_gateway_test.go index cf0b8754c2df..b4a4b15f0d23 100644 --- a/azurerm/resource_arm_virtual_network_gateway_test.go +++ b/azurerm/resource_arm_virtual_network_gateway_test.go @@ -36,6 +36,30 @@ func TestAccAzureRMVirtualNetworkGateway_basic(t *testing.T) { }) } +func TestAccAzureRMVirtualNetworkGateway_requiresImport(t *testing.T) { + resourceName := "azurerm_virtual_network_gateway.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualNetworkGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMVirtualNetworkGateway_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualNetworkGatewayExists(resourceName), + ), + }, + { + Config: testAccAzureRMVirtualNetworkGateway_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_virtual_network_gateway"), + }, + }, + }) +} + func TestAccAzureRMVirtualNetworkGateway_lowerCaseSubnetName(t *testing.T) { ri := acctest.RandInt() resourceName := "azurerm_virtual_network_gateway.test" @@ -279,6 +303,27 @@ resource "azurerm_virtual_network_gateway" "test" { } `, rInt, location, rInt, rInt, rInt) } +func testAccAzureRMVirtualNetworkGateway_requiresImport(rInt int, location string) string { + template := testAccAzureRMVirtualNetworkGateway_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_network_gateway" "import" { + name = "${azurerm_virtual_network_gateway.test.name}" + location = "${azurerm_virtual_network_gateway.test.location}" + resource_group_name = "${azurerm_virtual_network_gateway.test.resource_group_name}" + type = "${azurerm_virtual_network_gateway.test.type}" + vpn_type = "${azurerm_virtual_network_gateway.test.vpn_type}" + sku = "${azurerm_virtual_network_gateway.test.sku}" + + ip_configuration { + public_ip_address_id = "${azurerm_public_ip.test.id}" + private_ip_address_allocation = "Dynamic" + subnet_id = "${azurerm_subnet.test.id}" + } +} +`, template) +} func testAccAzureRMVirtualNetworkGateway_lowerCaseSubnetName(rInt int, location string) string { return fmt.Sprintf(` diff --git a/azurerm/resource_arm_virtual_network_peering.go b/azurerm/resource_arm_virtual_network_peering.go index d9be5c3592be..930a09036305 100644 --- a/azurerm/resource_arm_virtual_network_peering.go +++ b/azurerm/resource_arm_virtual_network_peering.go @@ -1,28 +1,37 @@ package azurerm import ( + "context" "fmt" "log" "sync" + "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" "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" ) +// TODO: switch over to using the main mutex // peerMutex is used to prevent multiple Peering resources being created, updated // or deleted at the same time var peerMutex = &sync.Mutex{} func resourceArmVirtualNetworkPeering() *schema.Resource { return &schema.Resource{ - Create: resourceArmVirtualNetworkPeeringCreate, + Create: resourceArmVirtualNetworkPeeringCreateUpdate, Read: resourceArmVirtualNetworkPeeringRead, - Update: resourceArmVirtualNetworkPeeringCreate, + Update: resourceArmVirtualNetworkPeeringCreateUpdate, Delete: resourceArmVirtualNetworkPeeringDelete, 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": { @@ -72,7 +81,7 @@ func resourceArmVirtualNetworkPeering() *schema.Resource { } } -func resourceArmVirtualNetworkPeeringCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmVirtualNetworkPeeringCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).vnetPeeringsClient ctx := meta.(*ArmClient).StopContext @@ -82,6 +91,20 @@ func resourceArmVirtualNetworkPeeringCreate(d *schema.ResourceData, meta interfa vnetName := d.Get("virtual_network_name").(string) resGroup := d.Get("resource_group_name").(string) + if d.IsNewResource() { + // first check if there's one in this subscription requiring import + resp, err := client.Get(ctx, resGroup, vnetName, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error checking for the existence of Peering %q (Virtual Network %q / Resource Group %q): %+v", name, vnetName, resGroup, err) + } + } + + if resp.ID != nil { + return tf.ImportAsExistsError("azurerm_virtual_network_peering", *resp.ID) + } + } + peer := network.VirtualNetworkPeering{ Name: &name, VirtualNetworkPeeringPropertiesFormat: getVirtualNetworkPeeringProperties(d), @@ -95,7 +118,9 @@ func resourceArmVirtualNetworkPeeringCreate(d *schema.ResourceData, meta interfa return fmt.Errorf("Error Creating/Updating Virtual Network Peering %q (Network %q / Resource Group %q): %+v", name, vnetName, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(tf.TimeoutForCreateUpdate(d))) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for completion of Virtual Network Peering %q (Network %q / Resource Group %q): %+v", name, vnetName, resGroup, err) } @@ -134,17 +159,20 @@ func resourceArmVirtualNetworkPeeringRead(d *schema.ResourceData, meta interface return fmt.Errorf("Error making Read request on Azure virtual network peering %q: %+v", name, err) } - peer := *resp.VirtualNetworkPeeringPropertiesFormat - - // update appropriate values - d.Set("resource_group_name", resGroup) d.Set("name", resp.Name) + d.Set("resource_group_name", resGroup) d.Set("virtual_network_name", vnetName) - d.Set("allow_virtual_network_access", peer.AllowVirtualNetworkAccess) - d.Set("allow_forwarded_traffic", peer.AllowForwardedTraffic) - d.Set("allow_gateway_transit", peer.AllowGatewayTransit) - d.Set("use_remote_gateways", peer.UseRemoteGateways) - d.Set("remote_virtual_network_id", peer.RemoteVirtualNetwork.ID) + + if props := resp.VirtualNetworkPeeringPropertiesFormat; props != nil { + d.Set("allow_virtual_network_access", props.AllowVirtualNetworkAccess) + d.Set("allow_forwarded_traffic", props.AllowForwardedTraffic) + d.Set("allow_gateway_transit", props.AllowGatewayTransit) + d.Set("use_remote_gateways", props.UseRemoteGateways) + + if network := props.RemoteVirtualNetwork; network != nil { + d.Set("remote_virtual_network_id", network.ID) + } + } return nil } @@ -157,6 +185,7 @@ func resourceArmVirtualNetworkPeeringDelete(d *schema.ResourceData, meta interfa if err != nil { return err } + resGroup := id.ResourceGroup vnetName := id.Path["virtualNetworks"] name := id.Path["virtualNetworkPeerings"] @@ -169,7 +198,9 @@ func resourceArmVirtualNetworkPeeringDelete(d *schema.ResourceData, meta interfa return fmt.Errorf("Error deleting Virtual Network Peering %q (Network %q / RG %q): %+v", name, vnetName, resGroup, err) } - err = future.WaitForCompletionRef(ctx, client.Client) + waitCtx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete)) + defer cancel() + err = future.WaitForCompletionRef(waitCtx, client.Client) if err != nil { return fmt.Errorf("Error waiting for deletion of Virtual Network Peering %q (Network %q / RG %q): %+v", name, vnetName, resGroup, err) } @@ -185,12 +216,12 @@ func getVirtualNetworkPeeringProperties(d *schema.ResourceData) *network.Virtual remoteVirtualNetworkID := d.Get("remote_virtual_network_id").(string) return &network.VirtualNetworkPeeringPropertiesFormat{ - AllowVirtualNetworkAccess: &allowVirtualNetworkAccess, - AllowForwardedTraffic: &allowForwardedTraffic, - AllowGatewayTransit: &allowGatewayTransit, - UseRemoteGateways: &useRemoteGateways, + AllowVirtualNetworkAccess: utils.Bool(allowVirtualNetworkAccess), + AllowForwardedTraffic: utils.Bool(allowForwardedTraffic), + AllowGatewayTransit: utils.Bool(allowGatewayTransit), + UseRemoteGateways: utils.Bool(useRemoteGateways), RemoteVirtualNetwork: &network.SubResource{ - ID: &remoteVirtualNetworkID, + ID: utils.String(remoteVirtualNetworkID), }, } } diff --git a/azurerm/resource_arm_virtual_network_peering_test.go b/azurerm/resource_arm_virtual_network_peering_test.go index fa68344d1504..c131b5d63c3e 100644 --- a/azurerm/resource_arm_virtual_network_peering_test.go +++ b/azurerm/resource_arm_virtual_network_peering_test.go @@ -35,6 +35,30 @@ func TestAccAzureRMVirtualNetworkPeering_basic(t *testing.T) { }) } +func TestAccAzureRMVirtualNetworkPeering_requiresImport(t *testing.T) { + firstResourceName := "azurerm_virtual_network_peering.test1" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualNetworkPeeringDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMVirtualNetworkPeering_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualNetworkPeeringExists(firstResourceName), + ), + }, + { + Config: testAccAzureRMVirtualNetworkPeering_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_virtual_network_peering"), + }, + }, + }) +} + func TestAccAzureRMVirtualNetworkPeering_disappears(t *testing.T) { firstResourceName := "azurerm_virtual_network_peering.test1" secondResourceName := "azurerm_virtual_network_peering.test2" @@ -232,6 +256,21 @@ resource "azurerm_virtual_network_peering" "test2" { `, rInt, location, rInt, rInt, rInt, rInt) } +func testAccAzureRMVirtualNetworkPeering_requiresImport(rInt int, location string) string { + template := testAccAzureRMVirtualNetworkPeering_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_network_peering" "import" { + name = "${azurerm_virtual_network_peering.test1.name}" + resource_group_name = "${azurerm_virtual_network_peering.test1.resource_group_name}" + virtual_network_name = "${azurerm_virtual_network_peering.test1.virtual_network_name}" + remote_virtual_network_id = "${azurerm_virtual_network_peering.test1.remote_virtual_network_id}" + allow_virtual_network_access = true +} +`, template) +} + func testAccAzureRMVirtualNetworkPeering_basicUpdate(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_virtual_network_test.go b/azurerm/resource_arm_virtual_network_test.go index 6dadede5c00b..3c04407e241e 100644 --- a/azurerm/resource_arm_virtual_network_test.go +++ b/azurerm/resource_arm_virtual_network_test.go @@ -98,6 +98,30 @@ func TestAccAzureRMVirtualNetwork_basic(t *testing.T) { }) } +func TestAccAzureRMVirtualNetwork_requiresImport(t *testing.T) { + resourceName := "azurerm_virtual_network.test" + ri := acctest.RandInt() + location := testLocation() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualNetworkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMVirtualNetwork_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualNetworkExists(resourceName), + ), + }, + { + Config: testAccAzureRMVirtualNetwork_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_virtual_network"), + }, + }, + }) +} + func TestAccAzureRMVirtualNetwork_disappears(t *testing.T) { resourceName := "azurerm_virtual_network.test" ri := acctest.RandInt() @@ -266,6 +290,25 @@ func testCheckAzureRMVirtualNetworkDestroy(s *terraform.State) error { return nil } +func testAccAzureRMVirtualNetwork_requiresImport(rInt int, location string) string { + template := testAccAzureRMVirtualNetwork_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_network" "import" { + name = "${azurerm_virtual_network.test.name}" + location = "${azurerm_virtual_network.test.location}" + resource_group_name = "${azurerm_virtual_network.test.resource_group_name}" + address_space = ["10.0.0.0/16"] + + subnet { + name = "subnet1" + address_prefix = "10.0.1.0/24" + } +} +`, template) +} + func testAccAzureRMVirtualNetwork_basic(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/vendor/github.com/Azure/go-autorest/autorest/azure/async.go b/vendor/github.com/Azure/go-autorest/autorest/azure/async.go index 9dd7a1d27c5f..b52c7acc2d44 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/azure/async.go +++ b/vendor/github.com/Azure/go-autorest/autorest/azure/async.go @@ -167,9 +167,16 @@ func (f Future) WaitForCompletion(ctx context.Context, client autorest.Client) e // running operation has completed, the provided context is cancelled, or the client's // polling duration has been exceeded. It will retry failed polling attempts based on // the retry value defined in the client up to the maximum retry attempts. -func (f *Future) WaitForCompletionRef(ctx context.Context, client autorest.Client) error { - ctx, cancel := context.WithTimeout(ctx, client.PollingDuration) +func (f *Future) WaitForCompletionRef(inputCtx context.Context, client autorest.Client) error { + var ctx context.Context + var cancel context.CancelFunc + if d := client.PollingDuration; d != 0 { + ctx, cancel = context.WithTimeout(inputCtx, d) + } else { + ctx = inputCtx + } defer cancel() + done, err := f.Done(client) for attempts := 0; !done; done, err = f.Done(client) { if attempts >= client.RetryAttempts { diff --git a/vendor/github.com/Azure/go-autorest/autorest/azure/rp.go b/vendor/github.com/Azure/go-autorest/autorest/azure/rp.go index bd34f0ed5a58..86ce9f2b5b1e 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/azure/rp.go +++ b/vendor/github.com/Azure/go-autorest/autorest/azure/rp.go @@ -140,8 +140,8 @@ func register(client autorest.Client, originalReq *http.Request, re RequestError } // poll for registered provisioning state - now := time.Now() - for err == nil && time.Since(now) < client.PollingDuration { + registrationStartTime := time.Now() + for err == nil && (client.PollingDuration == 0 || (client.PollingDuration != 0 && time.Since(registrationStartTime) < client.PollingDuration)) { // taken from the resources SDK // https://github.com/Azure/azure-sdk-for-go/blob/9f366792afa3e0ddaecdc860e793ba9d75e76c27/arm/resources/resources/providers.go#L45 preparer := autorest.CreatePreparer( @@ -183,7 +183,7 @@ func register(client autorest.Client, originalReq *http.Request, re RequestError return originalReq.Context().Err() } } - if !(time.Since(now) < client.PollingDuration) { + if client.PollingDuration != 0 && !(time.Since(registrationStartTime) < client.PollingDuration) { return errors.New("polling for resource provider registration has exceeded the polling duration") } return err diff --git a/website/azurerm.erb b/website/azurerm.erb index de3fa211570f..455e6c593438 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -476,6 +476,10 @@ azurerm_data_lake_store +