diff --git a/azurerm/config.go b/azurerm/config.go index 264718e337ab..9dc7fc953693 100644 --- a/azurerm/config.go +++ b/azurerm/config.go @@ -134,6 +134,7 @@ type ArmClient struct { sqlFirewallRulesClient sql.FirewallRulesClient sqlServersClient sql.ServersClient sqlServerAzureADAdministratorsClient sql.ServerAzureADAdministratorsClient + sqlVirtualNetworkRulesClient sql.VirtualNetworkRulesClient // KeyVault keyVaultClient keyvault.VaultsClient @@ -613,6 +614,10 @@ func (c *ArmClient) registerDatabases(endpoint, subscriptionId string, auth auto sqlADClient.Sender = sender sqlADClient.SkipResourceProviderRegistration = c.skipProviderRegistration c.sqlServerAzureADAdministratorsClient = sqlADClient + + sqlVNRClient := sql.NewVirtualNetworkRulesClientWithBaseURI(endpoint, subscriptionId) + c.configureClient(&sqlVNRClient.Client, auth) + c.sqlVirtualNetworkRulesClient = sqlVNRClient } func (c *ArmClient) registerDeviceClients(endpoint, subscriptionId string, auth autorest.Authorizer, sender autorest.Sender) { diff --git a/azurerm/import_arm_sql_virtual_network_rule_test.go b/azurerm/import_arm_sql_virtual_network_rule_test.go new file mode 100644 index 000000000000..02e8b3ed9e51 --- /dev/null +++ b/azurerm/import_arm_sql_virtual_network_rule_test.go @@ -0,0 +1,31 @@ +package azurerm + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccAzureRMSqlVirtualNetworkRule_importBasic(t *testing.T) { + resourceName := "azurerm_sql_virtual_network_rule.test" + + ri := acctest.RandInt() + config := testAccAzureRMSqlVirtualNetworkRule_basic(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlVirtualNetworkRuleDestroy, + Steps: []resource.TestStep{ + { + Config: config, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/azurerm/provider.go b/azurerm/provider.go index ecb2564cabf9..fd55f5f944d6 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -195,6 +195,7 @@ func Provider() terraform.ResourceProvider { "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(), "azurerm_storage_blob": resourceArmStorageBlob(), "azurerm_storage_container": resourceArmStorageContainer(), diff --git a/azurerm/resource_arm_sql_virtual_network_rule.go b/azurerm/resource_arm_sql_virtual_network_rule.go new file mode 100644 index 000000000000..19fbef32df86 --- /dev/null +++ b/azurerm/resource_arm_sql_virtual_network_rule.go @@ -0,0 +1,250 @@ +package azurerm + +import ( + "context" + "fmt" + "log" + "regexp" + "time" + + "github.com/Azure/azure-sdk-for-go/services/sql/mgmt/2015-05-01-preview/sql" + "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/utils" +) + +func resourceArmSqlVirtualNetworkRule() *schema.Resource { + return &schema.Resource{ + Create: resourceArmSqlVirtualNetworkRuleCreateUpdate, + Read: resourceArmSqlVirtualNetworkRuleRead, + Update: resourceArmSqlVirtualNetworkRuleCreateUpdate, + Delete: resourceArmSqlVirtualNetworkRuleDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateSqlVirtualNetworkRuleName, + }, + + "resource_group_name": resourceGroupNameSchema(), + + "server_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "subnet_id": { + Type: schema.TypeString, + Required: true, + }, + + "ignore_missing_vnet_service_endpoint": { + Type: schema.TypeBool, + Optional: true, + Default: false, //When not provided, Azure defaults to false + }, + }, + } +} + +func resourceArmSqlVirtualNetworkRuleCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).sqlVirtualNetworkRulesClient + ctx := meta.(*ArmClient).StopContext + + name := d.Get("name").(string) + serverName := d.Get("server_name").(string) + resourceGroup := d.Get("resource_group_name").(string) + virtualNetworkSubnetId := d.Get("subnet_id").(string) + ignoreMissingVnetServiceEndpoint := d.Get("ignore_missing_vnet_service_endpoint").(bool) + + parameters := sql.VirtualNetworkRule{ + VirtualNetworkRuleProperties: &sql.VirtualNetworkRuleProperties{ + VirtualNetworkSubnetID: utils.String(virtualNetworkSubnetId), + IgnoreMissingVnetServiceEndpoint: utils.Bool(ignoreMissingVnetServiceEndpoint), + }, + } + + _, 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) + } + + //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) + stateConf := &resource.StateChangeConf{ + Pending: []string{"Initializing", "InProgress", "Unknown", "ResponseNotFound"}, + Target: []string{"Ready"}, + Refresh: sqlVirtualNetworkStateStatusCodeRefreshFunc(ctx, client, resourceGroup, serverName, name), + Timeout: 10 * time.Minute, + MinTimeout: 1 * time.Minute, + ContinuousTargetOccurence: 5, + } + + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf("Error waiting for SQL Virtual Network Rule %q (SQL Server: %q, Resource Group: %q) to be created or updated: %+v", name, serverName, resourceGroup, err) + } + + resp, err := client.Get(ctx, resourceGroup, serverName, name) + if err != nil { + return fmt.Errorf("Error retrieving SQL Virtual Network Rule %q (SQL Server: %q, Resource Group: %q): %+v", name, serverName, resourceGroup, err) + } + + d.SetId(*resp.ID) + + return resourceArmSqlVirtualNetworkRuleRead(d, meta) +} + +func resourceArmSqlVirtualNetworkRuleRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).sqlVirtualNetworkRulesClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resourceGroup := id.ResourceGroup + serverName := id.Path["servers"] + name := id.Path["virtualNetworkRules"] + + resp, err := client.Get(ctx, resourceGroup, serverName, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] Error reading SQL Virtual Network Rule %q - removing from state", d.Id()) + d.SetId("") + return nil + } + + return fmt.Errorf("Error reading SQL Virtual Network Rule: %q (SQL Server: %q, Resource Group: %q): %+v", name, serverName, resourceGroup, err) + } + + d.Set("name", resp.Name) + d.Set("resource_group_name", resourceGroup) + d.Set("server_name", serverName) + + if props := resp.VirtualNetworkRuleProperties; props != nil { + d.Set("subnet_id", props.VirtualNetworkSubnetID) + d.Set("ignore_missing_vnet_service_endpoint", props.IgnoreMissingVnetServiceEndpoint) + } + + return nil +} + +func resourceArmSqlVirtualNetworkRuleDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).sqlVirtualNetworkRulesClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resourceGroup := id.ResourceGroup + serverName := id.Path["servers"] + name := id.Path["virtualNetworkRules"] + + future, err := client.Delete(ctx, resourceGroup, serverName, name) + if err != nil { + if response.WasNotFound(future.Response()) { + return nil + } + + return fmt.Errorf("Error deleting SQL Virtual Network Rule %q (SQL Server: %q, Resource Group: %q): %+v", name, serverName, resourceGroup, err) + } + + err = future.WaitForCompletion(ctx, client.Client) + if err != nil { + if response.WasNotFound(future.Response()) { + return nil + } + + return fmt.Errorf("Error deleting SQL Virtual Network Rule %q (SQL Server: %q, Resource Group: %q): %+v", name, serverName, resourceGroup, err) + } + + return nil +} + +/* + This function checks the format of the SQL Virtual Network Rule Name to make sure that + it does not contain any potentially invalid values. +*/ +func validateSqlVirtualNetworkRuleName(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + // Cannot be empty + if len(value) == 0 { + errors = append(errors, fmt.Errorf( + "%q cannot be an empty string: %q", k, value)) + } + + // Cannot be more than 128 characters + if len(value) > 128 { + errors = append(errors, fmt.Errorf( + "%q cannot be longer than 128 characters: %q", k, value)) + } + + // Must only contain alphanumeric characters or hyphens + if !regexp.MustCompile(`^[A-Za-z0-9-]*$`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "%q can only contain alphanumeric characters and hyphens: %q", + k, value)) + } + + // Cannot end in a hyphen + if regexp.MustCompile(`-$`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "%q cannot end with a hyphen: %q", k, value)) + } + + // Cannot start with a number or hyphen + if regexp.MustCompile(`^[0-9-]`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "%q cannot start with a number or hyphen: %q", k, value)) + } + + // There are multiple returns in the case that there is more than one invalid + // case applied to the name. + return +} + +/* + This function refreshes and checks the state of the SQL Virtual Network Rule. + + Response will contain a VirtualNetworkRuleProperties struct with a State property. The state property contain one of the following states (except ResponseNotFound). + * Deleting + * Initializing + * InProgress + * Unknown + * Ready + * ResponseNotFound (Custom state in case of 404) +*/ +func sqlVirtualNetworkStateStatusCodeRefreshFunc(ctx context.Context, client sql.VirtualNetworkRulesClient, resourceGroup string, serverName string, name string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + resp, err := client.Get(ctx, resourceGroup, serverName, name) + + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[DEBUG] Retrieving SQL Virtual Network Rule %q (SQL Server: %q, Resource Group: %q) returned 404.", resourceGroup, serverName, name) + return nil, "ResponseNotFound", nil + } + + return nil, "", fmt.Errorf("Error polling for the state of the SQL Virtual Network Rule %q (SQL Server: %q, Resource Group: %q): %+v", name, serverName, resourceGroup, err) + } + + if props := resp.VirtualNetworkRuleProperties; props != nil { + log.Printf("[DEBUG] Retrieving SQL Virtual Network Rule %q (SQL Server: %q, Resource Group: %q) returned Status %s", resourceGroup, serverName, name, props.State) + return resp, fmt.Sprintf("%s", props.State), nil + } + + //Valid response was returned but VirtualNetworkRuleProperties was nil. Basically the rule exists, but with no properties for some reason. Assume Unknown instead of returning error. + log.Printf("[DEBUG] Retrieving SQL Virtual Network Rule %q (SQL Server: %q, Resource Group: %q) returned empty VirtualNetworkRuleProperties", resourceGroup, serverName, name) + return resp, "Unknown", nil + } +} diff --git a/azurerm/resource_arm_sql_virtual_network_rule_test.go b/azurerm/resource_arm_sql_virtual_network_rule_test.go new file mode 100644 index 000000000000..3da6afbb0b9f --- /dev/null +++ b/azurerm/resource_arm_sql_virtual_network_rule_test.go @@ -0,0 +1,759 @@ +package azurerm + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "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() + preConfig := testAccAzureRMSqlVirtualNetworkRule_basic(ri, testLocation()) + postConfig := testAccAzureRMSqlVirtualNetworkRule_withUpdates(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlVirtualNetworkRuleDestroy, + Steps: []resource.TestStep{ + { + Config: preConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlVirtualNetworkRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "ignore_missing_vnet_service_endpoint", "false"), + ), + }, + { + Config: postConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlVirtualNetworkRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "ignore_missing_vnet_service_endpoint", "true"), + ), + }, + }, + }) +} + +/* + ---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_switchSubnets(t *testing.T) { + resourceName := "azurerm_sql_virtual_network_rule.test" + ri := acctest.RandInt() + + preConfig := testAccAzureRMSqlVirtualNetworkRule_subnetSwitchPre(ri, testLocation()) + postConfig := testAccAzureRMSqlVirtualNetworkRule_subnetSwitchPost(ri, testLocation()) + + // Create regex strings that will ensure that one subnet name exists, but not the other + preConfigRegex := regexp.MustCompile(fmt.Sprintf("(subnet1%d)$|(subnet[^2]%d)$", ri, ri)) //subnet 1 but not 2 + postConfigRegex := regexp.MustCompile(fmt.Sprintf("(subnet2%d)$|(subnet[^1]%d)$", ri, ri)) //subnet 2 but not 1 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlVirtualNetworkRuleDestroy, + Steps: []resource.TestStep{ + { + Config: preConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlVirtualNetworkRuleExists(resourceName), + resource.TestMatchResourceAttr(resourceName, "subnet_id", preConfigRegex), + ), + }, + { + Config: postConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlVirtualNetworkRuleExists(resourceName), + resource.TestMatchResourceAttr(resourceName, "subnet_id", postConfigRegex), + ), + }, + }, + }) +} + +/* + ---Testing for Success--- +*/ +func TestAccAzureRMSqlVirtualNetworkRule_disappears(t *testing.T) { + resourceName := "azurerm_sql_virtual_network_rule.test" + ri := acctest.RandInt() + config := testAccAzureRMSqlVirtualNetworkRule_basic(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlVirtualNetworkRuleDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlVirtualNetworkRuleExists(resourceName), + testCheckAzureRMSqlVirtualNetworkRuleDisappears(resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +/* + --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() + config := testAccAzureRMSqlVirtualNetworkRule_ignoreEndpointValid(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlVirtualNetworkRuleDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlVirtualNetworkRuleExists(resourceName), + ), + }, + }, + }) +} + +/* + --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()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlVirtualNetworkRuleDestroy, + Steps: []resource.TestStep{ + { + Config: config, + ExpectError: regexp.MustCompile("Code=\"VirtualNetworkRuleBadRequest\""), + }, + }, + }) +} + +/* + --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" + resourceName3 := "azurerm_sql_virtual_network_rule.rule3" + ri := acctest.RandInt() + config := testAccAzureRMSqlVirtualNetworkRule_multipleSubnets(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlVirtualNetworkRuleDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlVirtualNetworkRuleExists(resourceName1), + testCheckAzureRMSqlVirtualNetworkRuleExists(resourceName2), + testCheckAzureRMSqlVirtualNetworkRuleExists(resourceName3), + ), + }, + }, + }) +} + +/* + --Testing for Failure-- + Validation Function Tests - Invalid Name Validations +*/ +func TestResourceAzureRMSqlVirtualNetworkRule_invalidNameValidation(t *testing.T) { + cases := []struct { + Value string + ErrCount int + }{ + // Must only contain alphanumeric characters or hyphens (4 cases) + { + Value: "test!Rule", + ErrCount: 1, + }, + { + Value: "test_Rule", + ErrCount: 1, + }, + { + Value: "test:Rule", + ErrCount: 1, + }, + { + Value: "test'Rule", + ErrCount: 1, + }, + // Cannot be more than 128 characters (1 case - ensure starts with a letter) + { + Value: fmt.Sprintf("v%s", acctest.RandString(128)), + ErrCount: 1, + }, + // Cannot be empty (1 case) + { + Value: "", + ErrCount: 1, + }, + // Cannot end in a hyphen (1 case) + { + Value: "testRule-", + ErrCount: 1, + }, + // Cannot start with a number or hyphen (2 cases) + { + Value: "7testRule", + ErrCount: 1, + }, + { + Value: "-testRule", + ErrCount: 1, + }, + } + + for _, tc := range cases { + _, errors := validateSqlVirtualNetworkRuleName(tc.Value, "azurerm_sql_virtual_network_rule") + + if len(errors) != tc.ErrCount { + t.Fatalf("Bad: Expected the Azure RM SQL Virtual Network Rule Name to trigger a validation error.") + } + } +} + +/* + --Testing for Success-- + Validation Function Tests - (Barely) Valid Name Validations +*/ +func TestResourceAzureRMSqlVirtualNetworkRule_validNameValidation(t *testing.T) { + cases := []struct { + Value string + ErrCount int + }{ + // Test all lowercase + { + Value: "thisisarule", + ErrCount: 0, + }, + // Test all uppercase + { + Value: "THISISARULE", + ErrCount: 0, + }, + // Test alternating cases + { + Value: "tHiSiSaRuLe", + ErrCount: 0, + }, + // Test hyphens + { + Value: "this-is-a-rule", + ErrCount: 0, + }, + // Test multiple hyphens in a row + { + Value: "this----1s----a----ru1e", + ErrCount: 0, + }, + // Test numbers (except for first character) + { + Value: "v1108501298509850810258091285091820-5", + ErrCount: 0, + }, + // Test a lot of hyphens and numbers + { + Value: "x-5-4-1-2-5-2-6-1-5-2-5-1-2-5-6-2-2", + ErrCount: 0, + }, + // Test exactly 128 characters + { + Value: fmt.Sprintf("v%s", acctest.RandString(127)), + ErrCount: 0, + }, + // Test short, 1-letter name + { + Value: "V", + ErrCount: 0, + }, + } + + for _, tc := range cases { + _, errors := validateSqlVirtualNetworkRuleName(tc.Value, "azurerm_sql_virtual_network_rule") + + if len(errors) != tc.ErrCount { + t.Fatalf("Bad: Expected the Azure RM SQL Virtual Network Rule Name pass name validation successfully but triggered a validation error.") + } + } +} + +/* + 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] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + resourceGroup := rs.Primary.Attributes["resource_group_name"] + serverName := rs.Primary.Attributes["server_name"] + ruleName := rs.Primary.Attributes["name"] + + client := testAccProvider.Meta().(*ArmClient).sqlVirtualNetworkRulesClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, resourceGroup, serverName, ruleName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: SQL Firewall Rule %q (server %q / resource group %q) was not found", ruleName, serverName, resourceGroup) + } + + return err + } + + return nil + } +} + +/* + 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" { + continue + } + + resourceGroup := rs.Primary.Attributes["resource_group_name"] + serverName := rs.Primary.Attributes["server_name"] + ruleName := rs.Primary.Attributes["name"] + + client := testAccProvider.Meta().(*ArmClient).sqlVirtualNetworkRulesClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, resourceGroup, serverName, ruleName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return nil + } + + return err + } + + return fmt.Errorf("Bad: SQL Firewall Rule %q (server %q / resource group %q) still exists: %+v", ruleName, serverName, resourceGroup, resp) + } + + 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 + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + resourceGroup := rs.Primary.Attributes["resource_group_name"] + serverName := rs.Primary.Attributes["server_name"] + ruleName := rs.Primary.Attributes["name"] + + client := testAccProvider.Meta().(*ArmClient).sqlVirtualNetworkRulesClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + future, err := client.Delete(ctx, resourceGroup, serverName, ruleName) + if err != nil { + //If the error is that the resource we want to delete does not exist in the first + //place (404), then just return with no error. + if response.WasNotFound(future.Response()) { + return nil + } + + return fmt.Errorf("Error deleting SQL Virtual Network Rule: %+v", err) + } + + err = future.WaitForCompletion(ctx, client.Client) + if err != nil { + //Same deal as before. Just in case. + if response.WasNotFound(future.Response()) { + return nil + } + + return fmt.Errorf("Error deleting SQL Virtual Network Rule: %+v", err) + } + + return nil + } +} + +/* + (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" { + name = "acctestRG_%d" + location = "%s" +} +resource "azurerm_virtual_network" "test" { + name = "acctestvnet%d" + address_space = ["10.7.29.0/29"] + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" +} +resource "azurerm_subnet" "test" { + name = "acctestsubnet%d" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.7.29.0/29" + service_endpoints = ["Microsoft.Sql"] +} +resource "azurerm_sql_server" "test" { + name = "acctestsqlserver%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + version = "12.0" + administrator_login = "missadmin" + administrator_login_password = "${md5(%d)}!" +} +resource "azurerm_sql_virtual_network_rule" "test" { + name = "acctestsqlvnetrule%d" + resource_group_name = "${azurerm_resource_group.test.name}" + server_name = "${azurerm_sql_server.test.name}" + subnet_id = "${azurerm_subnet.test.id}" + ignore_missing_vnet_service_endpoint = false +} +`, 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_withUpdates(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG_%d" + location = "%s" +} +resource "azurerm_virtual_network" "test" { + name = "acctestvnet%d" + address_space = ["10.7.29.0/29"] + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" +} +resource "azurerm_subnet" "test" { + name = "acctestsubnet%d" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.7.29.0/29" + service_endpoints = ["Microsoft.Sql"] +} +resource "azurerm_sql_server" "test" { + name = "acctestsqlserver%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + version = "12.0" + administrator_login = "missadmin" + administrator_login_password = "${md5(%d)}!" +} +resource "azurerm_sql_virtual_network_rule" "test" { + name = "acctestsqlvnetrule%d" + resource_group_name = "${azurerm_resource_group.test.name}" + server_name = "${azurerm_sql_server.test.name}" + subnet_id = "${azurerm_subnet.test.id}" + ignore_missing_vnet_service_endpoint = true +} +`, 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" { + name = "acctestRG_%d" + location = "%s" +} +resource "azurerm_virtual_network" "test" { + name = "acctestvnet%d" + address_space = ["10.7.29.0/24"] + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" +} +resource "azurerm_subnet" "test1" { + name = "subnet1%d" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.7.29.0/25" + service_endpoints = ["Microsoft.Sql"] +} +resource "azurerm_subnet" "test2" { + name = "subnet2%d" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.7.29.128/25" + service_endpoints = ["Microsoft.Sql"] +} +resource "azurerm_sql_server" "test" { + name = "acctestsqlserver%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + version = "12.0" + administrator_login = "missadmin" + administrator_login_password = "${md5(%d)}!" +} +resource "azurerm_sql_virtual_network_rule" "test" { + name = "acctestsqlvnetrule%d" + resource_group_name = "${azurerm_resource_group.test.name}" + server_name = "${azurerm_sql_server.test.name}" + subnet_id = "${azurerm_subnet.test1.id}" +} +`, 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" { + name = "acctestRG_%d" + location = "%s" +} +resource "azurerm_virtual_network" "test" { + name = "acctestvnet%d" + address_space = ["10.7.29.0/24"] + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" +} +resource "azurerm_subnet" "test1" { + name = "subnet1%d" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.7.29.0/25" + service_endpoints = ["Microsoft.Sql"] +} +resource "azurerm_subnet" "test2" { + name = "subnet2%d" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.7.29.128/25" + service_endpoints = ["Microsoft.Sql"] +} +resource "azurerm_sql_server" "test" { + name = "acctestsqlserver%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + version = "12.0" + administrator_login = "missadmin" + administrator_login_password = "${md5(%d)}!" +} +resource "azurerm_sql_virtual_network_rule" "test" { + name = "acctestsqlvnetrule%d" + resource_group_name = "${azurerm_resource_group.test.name}" + server_name = "${azurerm_sql_server.test.name}" + subnet_id = "${azurerm_subnet.test2.id}" +} +`, 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" { + name = "acctestRG_%d" + location = "%s" +} +resource "azurerm_virtual_network" "test" { + name = "acctestvnet%d" + address_space = ["10.7.29.0/29"] + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" +} +resource "azurerm_subnet" "test" { + name = "acctestsubnet%d" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.7.29.0/29" + service_endpoints = ["Microsoft.Storage"] +} +resource "azurerm_sql_server" "test" { + name = "acctestsqlserver%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + version = "12.0" + administrator_login = "missadmin" + administrator_login_password = "${md5(%d)}!" +} +resource "azurerm_sql_virtual_network_rule" "test" { + name = "acctestsqlvnetrule%d" + resource_group_name = "${azurerm_resource_group.test.name}" + server_name = "${azurerm_sql_server.test.name}" + subnet_id = "${azurerm_subnet.test.id}" + ignore_missing_vnet_service_endpoint = true +} +`, 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" { + name = "acctestRG_%d" + location = "%s" +} +resource "azurerm_virtual_network" "test" { + name = "acctestvnet%d" + address_space = ["10.7.29.0/29"] + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" +} +resource "azurerm_subnet" "test" { + name = "acctestsubnet%d" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.7.29.0/29" + service_endpoints = ["Microsoft.Storage"] +} +resource "azurerm_sql_server" "test" { + name = "acctestsqlserver%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + version = "12.0" + administrator_login = "missadmin" + administrator_login_password = "${md5(%d)}!" +} +resource "azurerm_sql_virtual_network_rule" "test" { + name = "acctestsqlvnetrule%d" + resource_group_name = "${azurerm_resource_group.test.name}" + server_name = "${azurerm_sql_server.test.name}" + subnet_id = "${azurerm_subnet.test.id}" + ignore_missing_vnet_service_endpoint = false +} +`, 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" { + name = "acctestRG_%d" + location = "%s" +} +resource "azurerm_virtual_network" "vnet1" { + name = "acctestvnet1%d" + address_space = ["10.7.29.0/24"] + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" +} +resource "azurerm_virtual_network" "vnet2" { + name = "acctestvnet2%d" + address_space = ["10.1.29.0/29"] + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" +} +resource "azurerm_subnet" "vnet1_subnet1" { + name = "acctestsubnet1%d" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.vnet1.name}" + address_prefix = "10.7.29.0/29" + service_endpoints = ["Microsoft.Sql"] +} +resource "azurerm_subnet" "vnet1_subnet2" { + name = "acctestsubnet2%d" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.vnet1.name}" + address_prefix = "10.7.29.128/29" + service_endpoints = ["Microsoft.Sql"] +} +resource "azurerm_subnet" "vnet2_subnet1" { + name = "acctestsubnet3%d" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.vnet2.name}" + address_prefix = "10.1.29.0/29" + service_endpoints = ["Microsoft.Sql"] +} +resource "azurerm_sql_server" "test" { + name = "acctestsqlserver1%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + version = "12.0" + administrator_login = "missadmin" + administrator_login_password = "${md5(%d)}!" +} +resource "azurerm_sql_virtual_network_rule" "rule1" { + name = "acctestsqlvnetrule1%d" + resource_group_name = "${azurerm_resource_group.test.name}" + server_name = "${azurerm_sql_server.test.name}" + subnet_id = "${azurerm_subnet.vnet1_subnet1.id}" + ignore_missing_vnet_service_endpoint = false +} +resource "azurerm_sql_virtual_network_rule" "rule2" { + name = "acctestsqlvnetrule2%d" + resource_group_name = "${azurerm_resource_group.test.name}" + server_name = "${azurerm_sql_server.test.name}" + subnet_id = "${azurerm_subnet.vnet1_subnet2.id}" + ignore_missing_vnet_service_endpoint = false +} +resource "azurerm_sql_virtual_network_rule" "rule3" { + name = "acctestsqlvnetrule3%d" + resource_group_name = "${azurerm_resource_group.test.name}" + server_name = "${azurerm_sql_server.test.name}" + subnet_id = "${azurerm_subnet.vnet2_subnet1.id}" + ignore_missing_vnet_service_endpoint = false +} +`, rInt, location, rInt, rInt, rInt, rInt, rInt, rInt, rInt, rInt, rInt, rInt) +} diff --git a/website/azurerm.erb b/website/azurerm.erb index a1296ed11dff..a52abaebd877 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -353,6 +353,10 @@ azurerm_sql_server + > + sql_virtual_network_rule + + diff --git a/website/docs/r/sql_virtual_network_rule.html.markdown b/website/docs/r/sql_virtual_network_rule.html.markdown new file mode 100644 index 000000000000..74c12bb0ef0f --- /dev/null +++ b/website/docs/r/sql_virtual_network_rule.html.markdown @@ -0,0 +1,86 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_sql_virtual_network_rule" +sidebar_current: "docs-azurerm-resource-database-sql-virtual_network_rule" +description: |- + Create a SQL Virtual Network Rule. +--- + +# azurerm_sql_virtual_network_rule + +Allows you to add, update, or remove an Azure SQL server to a subnet of a virtual network. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-sql-server-vnet-rule" + location = "West US" +} + +resource "azurerm_virtual_network" "vnet" { + name = "example-vnet" + address_space = ["10.7.29.0/29"] + location = "${azurerm_resource_group.example.location}" + resource_group_name = "${azurerm_resource_group.example.name}" +} + +resource "azurerm_subnet" "subnet" { + name = "example-subnet" + resource_group_name = "${azurerm_resource_group.example.name}" + virtual_network_name = "${azurerm_virtual_network.vnet.name}" + address_prefix = "10.7.29.0/29" + service_endpoints = ["Microsoft.Sql"] +} + +resource "azurerm_sql_server" "sqlserver" { + name = "unqiueazuresqlserver" + resource_group_name = "${azurerm_resource_group.example.name}" + location = "${azurerm_resource_group.example.location}" + version = "12.0" + administrator_login = "4dm1n157r470r" + administrator_login_password = "4-v3ry-53cr37-p455w0rd" +} + +resource "azurerm_sql_virtual_network_rule" "sqlvnetrule" { + name = "sql-vnet-rule" + resource_group_name = "${azurerm_resource_group.example.name}" + server_name = "${azurerm_sql_server.sqlserver.name}" + subnet_id = "${azurerm_subnet.subnet.id}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the SQL virtual network rule. Changing this forces a new resource to be created. Cannot be empty and must only contain alphanumeric characters and hyphens. Cannot start with a number, and cannot start or end with a hyphen. + +~> **NOTE:** `name` must be between 1-128 characters long and must satisfy all of the requirements below: +1. Contains only alphanumeric and hyphen characters +2. Cannot start with a number or hyphen +3. Cannot end with a hyphen + +* `resource_group_name` - (Required) The name of the resource group where the SQL server resides. Changing this forces a new resource to be created. + +* `server_name` - (Required) The name of the SQL Server to which this SQL virtual network rule will be applied to. Changing this forces a new resource to be created. + +* `subnet_id` - (Required) The ID of the subnet that the SQL server will be connected to. + +* `ignore_missing_vnet_service_endpoint` - (Optional) Create the virtual network rule before the subnet has the virtual network service endpoint enabled. The default value is false. + +~> **NOTE:** If `ignore_missing_vnet_service_endpoint` is false, and the target subnet does not contain the `Microsoft.SQL` endpoint in the `service_endpoints` array, the deployment will fail when it tries to create the SQL virtual network rule. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the SQL virtual network rule. + +## Import + +SQL Virtual Network Rules can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_sql_virtual_network_rule.rule1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myresourcegroup/providers/Microsoft.Sql/servers/myserver/virtualNetworkRules/vnetrulename +```