Skip to content

Commit

Permalink
New Resource: azurerm_postgresql_virtual_network_rule (#1774)
Browse files Browse the repository at this point in the history
* Add the basics of postgresql vnet rule.

* Bring across postgresql_server resource from postgresql_server_test.

* Add doc page for postgresql_virtual_network_rule.

* Add import test for postgresql_virtual_network_rule.

* Make postgresql_virtual_network_rule example similar to postgresql_server.

* Fix resource ID for postgresql_virtual_network_rule import docs.

* Add postgresql_virtual_network_rule to docs sidebar.

* Add a client for postgresqlVirtualNetworkRules.

* Fix text in anchor for azurerm_postgresql_virtual_network_rule.

* Add non-zero value validation for server_name and subnet_id.

* (postgresql_virtual_network_rule) Adjust positioning of advice around new resource.

* Remove currently unused import for postgresql_virtual_network_rule.

* Fix validator function names for postgresql_virtual_network_rule.

* postgresql_virtual_network_rule Use Azure resource ID validator for subnet_id.

* postgresql_vnet_rule Acceptance tests mostly working, ignore missing is not.

* postgresql vnet rule - skip failing test because of API issue.

* postgresql vnet rule remove ignore_missing_vnet_service_endpoint test.

* Postgresql vnet rule - remove `ignore_missing_vnet_service_endpoint` attribute.

* postgresql vnet rule - default `IgnoreMissingVnetServiceEndpoint` to false, remove from read.

* Checking to ensure the Subnet's configured correctly so the Virtual Network Rule doesn't fail
  • Loading branch information
ac-astuartkregor authored and tombuildsstuff committed Sep 4, 2018
1 parent 6d05070 commit 8b1c9d1
Show file tree
Hide file tree
Showing 7 changed files with 1,041 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 @@ -142,6 +142,7 @@ type ArmClient struct {
postgresqlDatabasesClient postgresql.DatabasesClient
postgresqlFirewallRulesClient postgresql.FirewallRulesClient
postgresqlServersClient postgresql.ServersClient
postgresqlVirtualNetworkRulesClient postgresql.VirtualNetworkRulesClient
sqlDatabasesClient sql.DatabasesClient
sqlElasticPoolsClient sql.ElasticPoolsClient
sqlFirewallRulesClient sql.FirewallRulesClient
Expand Down Expand Up @@ -605,6 +606,10 @@ func (c *ArmClient) registerDatabases(endpoint, subscriptionId string, auth auto
c.configureClient(&postgresqlSrvClient.Client, auth)
c.postgresqlServersClient = postgresqlSrvClient

postgresqlVNRClient := postgresql.NewVirtualNetworkRulesClientWithBaseURI(endpoint, subscriptionId)
c.configureClient(&postgresqlVNRClient.Client, auth)
c.postgresqlVirtualNetworkRulesClient = postgresqlVNRClient

// SQL Azure
sqlDBClient := sql.NewDatabasesClientWithBaseURI(endpoint, subscriptionId)
c.configureClient(&sqlDBClient.Client, auth)
Expand Down
31 changes: 31 additions & 0 deletions azurerm/import_arm_postgresql_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 TestAccAzureRMPostgreSQLVirtualNetworkRule_importBasic(t *testing.T) {
resourceName := "azurerm_postgresql_virtual_network_rule.test"

ri := acctest.RandInt()
config := testAccAzureRMPostgreSQLVirtualNetworkRule_basic(ri, testLocation())

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMPostgreSQLVirtualNetworkRuleDestroy,
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 @@ -216,6 +216,7 @@ func Provider() terraform.ResourceProvider {
"azurerm_postgresql_database": resourceArmPostgreSQLDatabase(),
"azurerm_postgresql_firewall_rule": resourceArmPostgreSQLFirewallRule(),
"azurerm_postgresql_server": resourceArmPostgreSQLServer(),
"azurerm_postgresql_virtual_network_rule": resourceArmPostgreSQLVirtualNetworkRule(),
"azurerm_public_ip": resourceArmPublicIp(),
"azurerm_relay_namespace": resourceArmRelayNamespace(),
"azurerm_recovery_services_vault": resourceArmRecoveryServicesVault(),
Expand Down
284 changes: 284 additions & 0 deletions azurerm/resource_arm_postgresql_virtual_network_rule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
package azurerm

import (
"context"
"fmt"
"log"
"regexp"
"strings"
"time"

"github.com/Azure/azure-sdk-for-go/services/postgresql/mgmt/2017-12-01/postgresql"
"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/response"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

func resourceArmPostgreSQLVirtualNetworkRule() *schema.Resource {
return &schema.Resource{
Create: resourceArmPostgreSQLVirtualNetworkRuleCreateUpdate,
Read: resourceArmPostgreSQLVirtualNetworkRuleRead,
Update: resourceArmPostgreSQLVirtualNetworkRuleCreateUpdate,
Delete: resourceArmPostgreSQLVirtualNetworkRuleDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validatePostgreSQLVirtualNetworkRuleName,
},

"resource_group_name": resourceGroupNameSchema(),

"server_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.NoZeroValues,
},

"subnet_id": {
Type: schema.TypeString,
Required: true,
ValidateFunc: azure.ValidateResourceID,
},
},
}
}

func resourceArmPostgreSQLVirtualNetworkRuleCreateUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).postgresqlVirtualNetworkRulesClient
ctx := meta.(*ArmClient).StopContext

name := d.Get("name").(string)
serverName := d.Get("server_name").(string)
resourceGroup := d.Get("resource_group_name").(string)
subnetId := d.Get("subnet_id").(string)

// 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
subnetParsedId, err := parseAzureResourceID(subnetId)

subnetResourceGroup := subnetParsedId.ResourceGroup
virtualNetwork := subnetParsedId.Path["virtualNetworks"]
subnetName := subnetParsedId.Path["subnets"]
subnet, err := subnetsClient.Get(ctx, subnetResourceGroup, virtualNetwork, subnetName, "")
if err != nil {
if utils.ResponseWasNotFound(subnet.Response) {
return fmt.Errorf("Subnet with ID %q was not found: %+v", subnetId, err)
}

return fmt.Errorf("Error obtaining Subnet %q (Virtual Network %q / Resource Group %q: %+v", subnetName, virtualNetwork, subnetResourceGroup, err)
}

containsEndpoint := false
if props := subnet.SubnetPropertiesFormat; props != nil {
if endpoints := props.ServiceEndpoints; endpoints != nil {
for _, e := range *endpoints {
if e.Service == nil {
continue
}

if strings.EqualFold(*e.Service, "Microsoft.Sql") {
containsEndpoint = true
break
}
}
}
}

if !containsEndpoint {
return fmt.Errorf("Error creating PostgreSQL Virtual Network Rule: Subnet %q (Virtual Network %q / Resource Group %q) must contain a Service Endpoint for `Microsoft.Sql`", subnetName, virtualNetwork, subnetResourceGroup)
}

parameters := postgresql.VirtualNetworkRule{
VirtualNetworkRuleProperties: &postgresql.VirtualNetworkRuleProperties{
VirtualNetworkSubnetID: utils.String(subnetId),
IgnoreMissingVnetServiceEndpoint: utils.Bool(false),
},
}

_, err = client.CreateOrUpdate(ctx, resourceGroup, serverName, name, parameters)
if err != nil {
return fmt.Errorf("Error creating PostgreSQL Virtual Network Rule %q (PostgreSQL Server: %q, Resource Group: %q): %+v", name, serverName, resourceGroup, err)
}

//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)
stateConf := &resource.StateChangeConf{
Pending: []string{"Initializing", "InProgress", "Unknown", "ResponseNotFound"},
Target: []string{"Ready"},
Refresh: postgreSQLVirtualNetworkStateStatusCodeRefreshFunc(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 PostgreSQL Virtual Network Rule %q (PostgreSQL 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 PostgreSQL Virtual Network Rule %q (PostgreSQL Server: %q, Resource Group: %q): %+v", name, serverName, resourceGroup, err)
}

d.SetId(*resp.ID)

return resourceArmPostgreSQLVirtualNetworkRuleRead(d, meta)
}

func resourceArmPostgreSQLVirtualNetworkRuleRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).postgresqlVirtualNetworkRulesClient
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 PostgreSQL Virtual Network Rule %q - removing from state", d.Id())
d.SetId("")
return nil
}

return fmt.Errorf("Error reading PostgreSQL Virtual Network Rule: %q (PostgreSQL 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)
}

return nil
}

func resourceArmPostgreSQLVirtualNetworkRuleDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).postgresqlVirtualNetworkRulesClient
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 PostgreSQL Virtual Network Rule %q (PostgreSQL Server: %q, Resource Group: %q): %+v", name, serverName, resourceGroup, err)
}

err = future.WaitForCompletionRef(ctx, client.Client)
if err != nil {
if response.WasNotFound(future.Response()) {
return nil
}

return fmt.Errorf("Error deleting PostgreSQL Virtual Network Rule %q (PostgreSQL Server: %q, Resource Group: %q): %+v", name, serverName, resourceGroup, err)
}

return nil
}

/*
This function checks the format of the PostgreSQL Virtual Network Rule Name to make sure that
it does not contain any potentially invalid values.
*/
func validatePostgreSQLVirtualNetworkRuleName(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 PostgreSQL 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 postgreSQLVirtualNetworkStateStatusCodeRefreshFunc(ctx context.Context, client postgresql.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 PostgreSQL Virtual Network Rule %q (PostgreSQL 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 PostgreSQL Virtual Network Rule %q (PostgreSQL Server: %q, Resource Group: %q): %+v", name, serverName, resourceGroup, err)
}

if props := resp.VirtualNetworkRuleProperties; props != nil {
log.Printf("[DEBUG] Retrieving PostgreSQL Virtual Network Rule %q (PostgreSQL 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 PostgreSQL Virtual Network Rule %q (PostgreSQL Server: %q, Resource Group: %q) returned empty VirtualNetworkRuleProperties", resourceGroup, serverName, name)
return resp, "Unknown", nil
}
}
Loading

0 comments on commit 8b1c9d1

Please sign in to comment.