Skip to content

Commit

Permalink
Add resource for setup up vnet rules on MariaDB servers (#4048)
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel Intskirveli authored and katbyte committed Aug 18, 2019
1 parent 311b5cf commit fc4443e
Show file tree
Hide file tree
Showing 5 changed files with 872 additions and 8 deletions.
21 changes: 13 additions & 8 deletions azurerm/internal/services/mariadb/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import (
)

type Client struct {
ConfigurationsClient *mariadb.ConfigurationsClient
DatabasesClient *mariadb.DatabasesClient
FirewallRulesClient *mariadb.FirewallRulesClient
ServersClient *mariadb.ServersClient
ConfigurationsClient *mariadb.ConfigurationsClient
DatabasesClient *mariadb.DatabasesClient
FirewallRulesClient *mariadb.FirewallRulesClient
ServersClient *mariadb.ServersClient
VirtualNetworkRulesClient *mariadb.VirtualNetworkRulesClient
}

func BuildClient(o *common.ClientOptions) *Client {
Expand All @@ -26,10 +27,14 @@ func BuildClient(o *common.ClientOptions) *Client {
ServersClient := mariadb.NewServersClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&ServersClient.Client, o.ResourceManagerAuthorizer)

VirtualNetworkRulesClient := mariadb.NewVirtualNetworkRulesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&VirtualNetworkRulesClient.Client, o.ResourceManagerAuthorizer)

return &Client{
ConfigurationsClient: &configurationsClient,
DatabasesClient: &DatabasesClient,
FirewallRulesClient: &FirewallRulesClient,
ServersClient: &ServersClient,
ConfigurationsClient: &configurationsClient,
DatabasesClient: &DatabasesClient,
FirewallRulesClient: &FirewallRulesClient,
ServersClient: &ServersClient,
VirtualNetworkRulesClient: &VirtualNetworkRulesClient,
}
}
1 change: 1 addition & 0 deletions azurerm/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ func Provider() terraform.ResourceProvider {
"azurerm_mariadb_database": resourceArmMariaDbDatabase(),
"azurerm_mariadb_firewall_rule": resourceArmMariaDBFirewallRule(),
"azurerm_mariadb_server": resourceArmMariaDbServer(),
"azurerm_mariadb_virtual_network_rule": resourceArmMariaDbVirtualNetworkRule(),
"azurerm_media_services_account": resourceArmMediaServicesAccount(),
"azurerm_metric_alertrule": resourceArmMetricAlertRule(),
"azurerm_monitor_autoscale_setting": resourceArmMonitorAutoScaleSetting(),
Expand Down
242 changes: 242 additions & 0 deletions azurerm/resource_arm_mariadb_virtual_network_rule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
package azurerm

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

"github.com/Azure/azure-sdk-for-go/services/mariadb/mgmt/2018-06-01/mariadb"
"github.com/hashicorp/terraform/helper/resource"
"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"
)

func resourceArmMariaDbVirtualNetworkRule() *schema.Resource {
return &schema.Resource{
Create: resourceArmMariaDbVirtualNetworkRuleCreateUpdate,
Read: resourceArmMariaDbVirtualNetworkRuleRead,
Update: resourceArmMariaDbVirtualNetworkRuleCreateUpdate,
Delete: resourceArmMariaDbVirtualNetworkRuleDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

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

"resource_group_name": azure.SchemaResourceGroupName(),

"server_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validate.NoEmptyStrings,
},

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

func resourceArmMariaDbVirtualNetworkRuleCreateUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).mariadb.VirtualNetworkRulesClient
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)

if requireResourcesToBeImported && d.IsNewResource() {
existing, err := client.Get(ctx, resourceGroup, serverName, name)
if err != nil {
if !utils.ResponseWasNotFound(existing.Response) {
return fmt.Errorf("Error checking for presence of existing MariaDb Virtual Network Rule %q (MariaDb Server: %q, Resource Group: %q): %+v", name, serverName, resourceGroup, err)
}
}

if existing.ID != nil && *existing.ID != "" {
return tf.ImportAsExistsError("azurerm_mariadb_virtual_network_rule", *existing.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).network.SubnetsClient
subnetParsedId, err := parseAzureResourceID(subnetId)
if err != nil {
return err
}

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 MariaDb Virtual Network Rule: Subnet %q (Virtual Network %q / Resource Group %q) must contain a Service Endpoint for `Microsoft.Sql`", subnetName, virtualNetwork, subnetResourceGroup)
}

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

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

//Wait for the provisioning state to become ready
log.Printf("[DEBUG] Waiting for MariaDb Virtual Network Rule %q (MariaDb 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: MariaDbVirtualNetworkStateStatusCodeRefreshFunc(ctx, client, resourceGroup, serverName, name),
Timeout: 30 * time.Minute,
MinTimeout: 1 * time.Minute,
ContinuousTargetOccurence: 5,
}

if _, err = stateConf.WaitForState(); err != nil {
return fmt.Errorf("Error waiting for MariaDb Virtual Network Rule %q (MariaDb 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 MariaDb Virtual Network Rule %q (MariaDb Server: %q, Resource Group: %q): %+v", name, serverName, resourceGroup, err)
}

d.SetId(*resp.ID)

return resourceArmMariaDbVirtualNetworkRuleRead(d, meta)
}

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

return fmt.Errorf("Error reading MariaDb Virtual Network Rule: %q (MariaDb 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 resourceArmMariaDbVirtualNetworkRuleDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).mariadb.VirtualNetworkRulesClient
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 MariaDb Virtual Network Rule %q (MariaDb Server: %q, Resource Group: %q): %+v", name, serverName, resourceGroup, err)
}

if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
if !response.WasNotFound(future.Response()) {
return fmt.Errorf("Error waiting for deletion of MariaDb Virtual Network Rule %q (MariaDb Server: %q, Resource Group: %q): %+v", name, serverName, resourceGroup, err)
}
}

return nil
}

func MariaDbVirtualNetworkStateStatusCodeRefreshFunc(ctx context.Context, client *mariadb.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 MariaDb Virtual Network Rule %q (MariaDb 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 MariaDb Virtual Network Rule %q (MariaDb Server: %q, Resource Group: %q): %+v", name, serverName, resourceGroup, err)
}

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

0 comments on commit fc4443e

Please sign in to comment.