Skip to content

Commit

Permalink
New Resource: azurerm_sql_virtual_network_rule (#978)
Browse files Browse the repository at this point in the history
* Added SQL Vnet Rule

* Initial working implemtation of arm_sql_virtual_network_rule

* Added initial virtual network rule test.

* Fixed SQL test cases for SQL Vnet rule.

* Fixed SQL Virtual Network Rule tests to run correctly.

* Added additional test cases and expected output for SQL Vnet rule endpoint cases

* Added Import Test for SQL Vnet Rule.

* Added additional test case for provisioning multiple SQL vnet rules

* Fixed formatting and comments in vnet sql rule tests.

* Updated config entry to new registration syntax

* Updated subnet variable name to match other resources

* Added SQL vnet rule name validation and tests

* Added additional error handling to the SQL vnet rule delete functions.

* Added nil check for response properties to SQL vnet rule.

* Reordered SQL vnet rule name validation for error message clarity

* Added SQL vnet rule test for switching subnets

* Added additional validation function acceptance test for SQL vnet rule

* Fixed SQL Vnet Rule test case and validation logic. Added more consistency with comments.

* Added initial rough draft of documentation

* Added SQL vnet rule endpoint_state as a computed field

* Revised part of the SQL vnet rule documentation.

* Added check/wait status on create or update of a SQL virtual network rule. Added consistency to error messages.

* Increased SQL Vnet Rule Create method's ContinuousTargetOccurence property to 5.

* Removing the `endpoint_state` field
  • Loading branch information
ShepardToTheStars authored and tombuildsstuff committed Mar 28, 2018
1 parent 4cda1c3 commit 8446bb3
Show file tree
Hide file tree
Showing 7 changed files with 1,136 additions and 0 deletions.
5 changes: 5 additions & 0 deletions azurerm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ type ArmClient struct {
sqlFirewallRulesClient sql.FirewallRulesClient
sqlServersClient sql.ServersClient
sqlServerAzureADAdministratorsClient sql.ServerAzureADAdministratorsClient
sqlVirtualNetworkRulesClient sql.VirtualNetworkRulesClient

// KeyVault
keyVaultClient keyvault.VaultsClient
Expand Down Expand Up @@ -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) {
Expand Down
31 changes: 31 additions & 0 deletions azurerm/import_arm_sql_virtual_network_rule_test.go
Original file line number Diff line number Diff line change
@@ -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,
},
},
})
}
1 change: 1 addition & 0 deletions azurerm/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
250 changes: 250 additions & 0 deletions azurerm/resource_arm_sql_virtual_network_rule.go
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading

0 comments on commit 8446bb3

Please sign in to comment.