Skip to content

Commit

Permalink
r/azurestack_subnet_route_table_association: new resource
Browse files Browse the repository at this point in the history
  • Loading branch information
simonbrady committed Apr 30, 2024
1 parent 917973c commit bcc14ee
Show file tree
Hide file tree
Showing 5 changed files with 593 additions and 1 deletion.
3 changes: 2 additions & 1 deletion internal/services/network/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource {
"azurestack_local_network_gateway": localNetworkGateway(),
"azurestack_virtual_network_peering": virtualNetworkPeering(),
"azurestack_network_interface_backend_address_pool_association": loadBalancerBackendAddressPoolAssociation(),
"azurestack_subnet_network_security_group_association": subnetNetworkSecurityGroupAssociation(),
"azurestack_network_interface_security_group_association": networkInterfaceSecurityGroupAssociation(),
"azurestack_subnet_network_security_group_association": subnetNetworkSecurityGroupAssociation(),
"azurestack_subnet_route_table_association": subnetRouteTableAssociation(),
}
}
27 changes: 27 additions & 0 deletions internal/services/network/subnet_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,33 @@ func (SubnetResource) hasNoNetworkSecurityGroup(ctx context.Context, client *cli
return nil
}

func (SubnetResource) hasNoRouteTable(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) error {
id, err := parse.SubnetID(state.ID)
if err != nil {
return err
}

resp, err := client.Network.SubnetsClient.Get(ctx, id.ResourceGroup, id.VirtualNetworkName, id.Name, "")
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
return fmt.Errorf("Bad: Subnet %q (Virtual Network %q / Resource Group: %q) does not exist", id.Name, id.VirtualNetworkName, id.ResourceGroup)
}

return fmt.Errorf("Bad: Get on subnetClient: %+v", err)
}

props := resp.SubnetPropertiesFormat
if props == nil {
return fmt.Errorf("Properties was nil for Subnet %q (Virtual Network %q / Resource Group: %q)", id.Name, id.VirtualNetworkName, id.ResourceGroup)
}

if props.RouteTable != nil && ((props.RouteTable.ID == nil) || (props.RouteTable.ID != nil && *props.RouteTable.ID == "")) {
return fmt.Errorf("No Route Table should exist for Subnet %q (Virtual Network %q / Resource Group: %q) but got %q", id.Name, id.VirtualNetworkName, id.ResourceGroup, *props.RouteTable.ID)
}

return nil
}

func (r SubnetResource) basic(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
Expand Down
257 changes: 257 additions & 0 deletions internal/services/network/subnet_route_table_association_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
package network

import (
"fmt"
"log"
"time"

"github.com/Azure/azure-sdk-for-go/profiles/2020-09-01/network/mgmt/network"
"github.com/hashicorp/terraform-provider-azurestack/internal/clients"
"github.com/hashicorp/terraform-provider-azurestack/internal/locks"
"github.com/hashicorp/terraform-provider-azurestack/internal/services/network/parse"
"github.com/hashicorp/terraform-provider-azurestack/internal/services/network/validate"
"github.com/hashicorp/terraform-provider-azurestack/internal/tf"
"github.com/hashicorp/terraform-provider-azurestack/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurestack/internal/tf/timeouts"
"github.com/hashicorp/terraform-provider-azurestack/internal/utils"
)

func subnetRouteTableAssociation() *pluginsdk.Resource {
return &pluginsdk.Resource{
Create: subnetRouteTableAssociationCreate,
Read: subnetRouteTableAssociationRead,
Delete: subnetRouteTableAssociationDelete,

Timeouts: &pluginsdk.ResourceTimeout{
Create: pluginsdk.DefaultTimeout(30 * time.Minute),
Read: pluginsdk.DefaultTimeout(5 * time.Minute),
Update: pluginsdk.DefaultTimeout(30 * time.Minute),
Delete: pluginsdk.DefaultTimeout(30 * time.Minute),
},

Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error {
_, err := parse.SubnetID(id)
return err
}),

Schema: map[string]*pluginsdk.Schema{
"subnet_id": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validate.SubnetID,
},

"route_table_id": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validate.RouteTableID,
},
},
}
}

func subnetRouteTableAssociationCreate(d *pluginsdk.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Network.SubnetsClient
vnetClient := meta.(*clients.Client).Network.VnetClient
ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d)
defer cancel()

log.Printf("[INFO] preparing arguments for Subnet <-> Route Table Association creation.")

subnetId := d.Get("subnet_id").(string)
routeTableId := d.Get("route_table_id").(string)

parsedSubnetId, err := parse.SubnetID(subnetId)
if err != nil {
return err
}

parsedRouteTableId, err := parse.RouteTableID(routeTableId)
if err != nil {
return err
}

locks.ByName(parsedRouteTableId.Name, routeTableResourceName)
defer locks.UnlockByName(parsedRouteTableId.Name, routeTableResourceName)

subnetName := parsedSubnetId.Name
virtualNetworkName := parsedSubnetId.VirtualNetworkName
resourceGroup := parsedSubnetId.ResourceGroup

locks.ByName(virtualNetworkName, VirtualNetworkResourceName)
defer locks.UnlockByName(virtualNetworkName, VirtualNetworkResourceName)

subnet, err := client.Get(ctx, resourceGroup, virtualNetworkName, subnetName, "")
if err != nil {
if utils.ResponseWasNotFound(subnet.Response) {
return fmt.Errorf("Subnet %q (Virtual Network %q / Resource Group %q) was not found!", subnetName, virtualNetworkName, resourceGroup)
}

return fmt.Errorf("retrieving Subnet %q (Virtual Network %q / Resource Group %q): %+v", subnetName, virtualNetworkName, resourceGroup, err)
}

if props := subnet.SubnetPropertiesFormat; props != nil {
if rt := props.RouteTable; rt != nil {
// we're intentionally not checking the ID - if there's a RouteTable, it needs to be imported
if rt.ID != nil && subnet.ID != nil {
return tf.ImportAsExistsError("azurestack_subnet_route_table_association", *subnet.ID)
}
}

props.RouteTable = &network.RouteTable{
ID: utils.String(routeTableId),
}
}

future, err := client.CreateOrUpdate(ctx, resourceGroup, virtualNetworkName, subnetName, subnet)
if err != nil {
return fmt.Errorf("updating Route Table Association for Subnet %q (Virtual Network %q / Resource Group %q): %+v", subnetName, virtualNetworkName, resourceGroup, err)
}

if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
return fmt.Errorf("waiting for completion of Route Table Association for Subnet %q (VN %q / Resource Group %q): %+v", subnetName, virtualNetworkName, resourceGroup, err)
}

timeout, _ := ctx.Deadline()

stateConf := &pluginsdk.StateChangeConf{
Pending: []string{string(network.Updating)},
Target: []string{string(network.Succeeded)},
Refresh: SubnetProvisioningStateRefreshFunc(ctx, client, *parsedSubnetId),
MinTimeout: 1 * time.Minute,
Timeout: time.Until(timeout),
}
if _, err = stateConf.WaitForStateContext(ctx); err != nil {
return fmt.Errorf("waiting for provisioning state of subnet for Route Table Association for Subnet %q (Virtual Network %q / Resource Group %q): %+v", subnetName, virtualNetworkName, resourceGroup, err)
}

vnetId := parse.NewVirtualNetworkID(parsedSubnetId.SubscriptionId, parsedSubnetId.ResourceGroup, parsedSubnetId.VirtualNetworkName)
vnetStateConf := &pluginsdk.StateChangeConf{
Pending: []string{string(network.Updating)},
Target: []string{string(network.Succeeded)},
Refresh: VirtualNetworkProvisioningStateRefreshFunc(ctx, vnetClient, vnetId),
MinTimeout: 1 * time.Minute,
Timeout: time.Until(timeout),
}
if _, err = vnetStateConf.WaitForStateContext(ctx); err != nil {
return fmt.Errorf("waiting for provisioning state of virtual network for Route Table Association for Subnet %q (Virtual Network %q / Resource Group %q): %+v", subnetName, virtualNetworkName, resourceGroup, err)
}

d.SetId(parsedSubnetId.ID())

return subnetRouteTableAssociationRead(d, meta)
}

func subnetRouteTableAssociationRead(d *pluginsdk.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Network.SubnetsClient
ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
defer cancel()

id, err := parse.SubnetID(d.Id())
if err != nil {
return err
}
resourceGroup := id.ResourceGroup
virtualNetworkName := id.VirtualNetworkName
subnetName := id.Name

resp, err := client.Get(ctx, resourceGroup, virtualNetworkName, subnetName, "")
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
log.Printf("[DEBUG] Subnet %q (Virtual Network %q / Resource Group %q) could not be found - removing from state!", subnetName, virtualNetworkName, resourceGroup)
d.SetId("")
return nil
}
return fmt.Errorf("retrieving Subnet %q (Virtual Network %q / Resource Group %q): %+v", subnetName, virtualNetworkName, resourceGroup, err)
}

props := resp.SubnetPropertiesFormat
if props == nil {
return fmt.Errorf("Error: `properties` was nil for Subnet %q (Virtual Network %q / Resource Group %q)", subnetName, virtualNetworkName, resourceGroup)
}

routeTable := props.RouteTable
if routeTable == nil {
log.Printf("[DEBUG] Subnet %q (Virtual Network %q / Resource Group %q) doesn't have a Route Table - removing from state!", subnetName, virtualNetworkName, resourceGroup)
d.SetId("")
return nil
}

d.Set("subnet_id", resp.ID)
d.Set("route_table_id", routeTable.ID)

return nil
}

func subnetRouteTableAssociationDelete(d *pluginsdk.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Network.SubnetsClient
ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d)
defer cancel()

id, err := parse.SubnetID(d.Id())
if err != nil {
return err
}
resourceGroup := id.ResourceGroup
virtualNetworkName := id.VirtualNetworkName
subnetName := id.Name

// retrieve the subnet
read, err := client.Get(ctx, resourceGroup, virtualNetworkName, subnetName, "")
if err != nil {
if utils.ResponseWasNotFound(read.Response) {
log.Printf("[DEBUG] Subnet %q (Virtual Network %q / Resource Group %q) could not be found - removing from state!", subnetName, virtualNetworkName, resourceGroup)
return nil
}

return fmt.Errorf("retrieving Subnet %q (Virtual Network %q / Resource Group %q): %+v", subnetName, virtualNetworkName, resourceGroup, err)
}

props := read.SubnetPropertiesFormat
if props == nil {
return fmt.Errorf("`Properties` was nil for Subnet %q (Virtual Network %q / Resource Group %q)", subnetName, virtualNetworkName, resourceGroup)
}

if props.RouteTable == nil || props.RouteTable.ID == nil {
log.Printf("[DEBUG] Subnet %q (Virtual Network %q / Resource Group %q) has no Route Table - removing from state!", subnetName, virtualNetworkName, resourceGroup)
return nil
}

// once we have the route table id to lock on, lock on that
parsedRouteTableId, err := parse.RouteTableID(*props.RouteTable.ID)
if err != nil {
return err
}

locks.ByName(parsedRouteTableId.Name, routeTableResourceName)
defer locks.UnlockByName(parsedRouteTableId.Name, routeTableResourceName)

locks.ByName(virtualNetworkName, VirtualNetworkResourceName)
defer locks.UnlockByName(virtualNetworkName, VirtualNetworkResourceName)

// then re-retrieve it to ensure we've got the latest state
read, err = client.Get(ctx, resourceGroup, virtualNetworkName, subnetName, "")
if err != nil {
if utils.ResponseWasNotFound(read.Response) {
log.Printf("[DEBUG] Subnet %q (Virtual Network %q / Resource Group %q) could not be found - removing from state!", subnetName, virtualNetworkName, resourceGroup)
return nil
}

return fmt.Errorf("retrieving Subnet %q (Virtual Network %q / Resource Group %q): %+v", subnetName, virtualNetworkName, resourceGroup, err)
}

read.SubnetPropertiesFormat.RouteTable = nil

future, err := client.CreateOrUpdate(ctx, resourceGroup, virtualNetworkName, subnetName, read)
if err != nil {
return fmt.Errorf("removing Route Table Association from Subnet %q (Virtual Network %q / Resource Group %q): %+v", subnetName, virtualNetworkName, resourceGroup, err)
}

if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
return fmt.Errorf("waiting for removal of Route Table Association from Subnet %q (Virtual Network %q / Resource Group %q): %+v", subnetName, virtualNetworkName, resourceGroup, err)
}

return nil
}
Loading

0 comments on commit bcc14ee

Please sign in to comment.