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 + > + azurerm_data_lake_store_file + + > azurerm_data_lake_store_firewall_rule diff --git a/website/docs/r/container_service.html.markdown b/website/docs/r/container_service.html.markdown index f417c61bb1fe..58353d6dc1fe 100644 --- a/website/docs/r/container_service.html.markdown +++ b/website/docs/r/container_service.html.markdown @@ -216,3 +216,11 @@ The following attributes are exported: * `agent_pool_profile.fqdn` - FDQN for the agent pool. * `diagnostics_profile.storage_uri` - The URI of the storage account where diagnostics are stored. + +## Import + +Container Services can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_container_service.test /subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/mygroup1/providers/Microsoft.ContainerService/containerServices/containersvc1 +``` diff --git a/website/docs/r/data_lake_analytics_firewall_rule.html.markdown b/website/docs/r/data_lake_analytics_firewall_rule.html.markdown index cceeae36d808..9558416e7c28 100644 --- a/website/docs/r/data_lake_analytics_firewall_rule.html.markdown +++ b/website/docs/r/data_lake_analytics_firewall_rule.html.markdown @@ -34,7 +34,7 @@ resource "azurerm_data_lake_analytics_account" "example" { resource "azurerm_data_lake_analytics_firewall_rule" "example" { name = "office-ip-range" - account_name = "${azurerm_data_lake_analytics.example.name}" + account_name = "${azurerm_data_lake_analytics_account.example.name}" resource_group_name = "${azurerm_resource_group.example.name}" start_ip_address = "1.2.3.4" end_ip_address = "2.3.4.5" diff --git a/website/docs/r/template_deployment.html.markdown b/website/docs/r/template_deployment.html.markdown index 07a7b9339763..82861d9469e3 100644 --- a/website/docs/r/template_deployment.html.markdown +++ b/website/docs/r/template_deployment.html.markdown @@ -3,12 +3,12 @@ layout: "azurerm" page_title: "Azure Resource Manager: azurerm_template_deployment" sidebar_current: "docs-azurerm-resource-template-deployment" description: |- - Manages a template deployment of resources. + Manages the deployment of an ARM Template. --- # azurerm_template_deployment -Manage a template deployment of resources +Manages the deployment of an ARM Template ~> **Note on ARM Template Deployments:** Due to the way the underlying Azure API is designed, Terraform can only manage the deployment of the ARM Template - and not any resources which are created by it. This means that when deleting the `azurerm_template_deployment` resource, Terraform will only remove the reference to the deployment, whilst leaving any resources created by that ARM Template Deployment. @@ -131,3 +131,12 @@ The following attributes are exported: ## Note Terraform does not know about the individual resources created by Azure using a deployment template and therefore cannot delete these resources during a destroy. Destroying a template deployment removes the associated deployment operations, but will not delete the Azure resources created by the deployment. In order to delete these resources, the containing resource group must also be destroyed. [More information](https://docs.microsoft.com/en-us/rest/api/resources/deployments#Deployments_Delete). + +## Import + +Template Deployments can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_template_deployment.test /subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/group1/providers/Microsoft.Resources/deployments/deployment1 +``` +