From bcc14eecd64b255f84a5938aa5522736b363544c Mon Sep 17 00:00:00 2001 From: Simon Brady Date: Wed, 10 May 2023 19:45:02 +1200 Subject: [PATCH] r/azurestack_subnet_route_table_association: new resource --- internal/services/network/registration.go | 3 +- .../services/network/subnet_resource_test.go | 27 ++ ...subnet_route_table_association_resource.go | 257 ++++++++++++++++++ ...t_route_table_association_resource_test.go | 223 +++++++++++++++ ...bnet_route_table_association.html.markdown | 84 ++++++ 5 files changed, 593 insertions(+), 1 deletion(-) create mode 100644 internal/services/network/subnet_route_table_association_resource.go create mode 100644 internal/services/network/subnet_route_table_association_resource_test.go create mode 100644 website/docs/r/subnet_route_table_association.html.markdown diff --git a/internal/services/network/registration.go b/internal/services/network/registration.go index 8eb76eb3c..41988efa7 100644 --- a/internal/services/network/registration.go +++ b/internal/services/network/registration.go @@ -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(), } } diff --git a/internal/services/network/subnet_resource_test.go b/internal/services/network/subnet_resource_test.go index 3374d3403..597216de3 100644 --- a/internal/services/network/subnet_resource_test.go +++ b/internal/services/network/subnet_resource_test.go @@ -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 diff --git a/internal/services/network/subnet_route_table_association_resource.go b/internal/services/network/subnet_route_table_association_resource.go new file mode 100644 index 000000000..c0a031739 --- /dev/null +++ b/internal/services/network/subnet_route_table_association_resource.go @@ -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 +} diff --git a/internal/services/network/subnet_route_table_association_resource_test.go b/internal/services/network/subnet_route_table_association_resource_test.go new file mode 100644 index 000000000..65199905a --- /dev/null +++ b/internal/services/network/subnet_route_table_association_resource_test.go @@ -0,0 +1,223 @@ +package network_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-provider-azurestack/internal/clients" + "github.com/hashicorp/terraform-provider-azurestack/internal/services/network/parse" + "github.com/hashicorp/terraform-provider-azurestack/internal/tf/acceptance" + "github.com/hashicorp/terraform-provider-azurestack/internal/tf/acceptance/check" + "github.com/hashicorp/terraform-provider-azurestack/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurestack/internal/utils" +) + +type SubnetRouteTableAssociationResource struct{} + +func TestAccSubnetRouteTableAssociation_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurestack_subnet_route_table_association", "test") + r := SubnetRouteTableAssociationResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + // intentional since this is a Virtual Resource + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccSubnetRouteTableAssociation_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurestack_subnet_route_table_association", "test") + r := SubnetRouteTableAssociationResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + // intentional since this is a Virtual Resource + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + { + Config: r.requiresImport(data), + ExpectError: acceptance.RequiresImportError("azurestack_subnet_route_table_association"), + }, + }) +} + +func TestAccSubnetRouteTableAssociation_updateSubnet(t *testing.T) { + data := acceptance.BuildTestData(t, "azurestack_subnet_route_table_association", "test") + r := SubnetRouteTableAssociationResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + // intentional since this is a Virtual Resource + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.updateSubnet(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccSubnetRouteTableAssociation_deleted(t *testing.T) { + data := acceptance.BuildTestData(t, "azurestack_subnet_route_table_association", "test") + r := SubnetRouteTableAssociationResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + // NOTE: intentionally not using a DisappearsStep as this is a Virtual Resource + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + data.CheckWithClient(r.destroy), + data.CheckWithClientForResource(SubnetResource{}.hasNoRouteTable, "azurestack_subnet.test"), + ), + ExpectNonEmptyPlan: true, + }, + }) +} + +func (SubnetRouteTableAssociationResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.SubnetID(state.ID) + if err != nil { + return nil, err + } + resourceGroup := id.ResourceGroup + virtualNetworkName := id.VirtualNetworkName + subnetName := id.Name + + resp, err := clients.Network.SubnetsClient.Get(ctx, resourceGroup, virtualNetworkName, subnetName, "") + if err != nil { + return nil, fmt.Errorf("reading Subnet Route Table Association (%s): %+v", id, err) + } + + props := resp.SubnetPropertiesFormat + if props == nil || props.RouteTable == nil { + return nil, fmt.Errorf("properties was nil for Subnet %q (Virtual Network %q / Resource Group: %q)", subnetName, virtualNetworkName, resourceGroup) + } + + return utils.Bool(props.RouteTable.ID != nil), nil +} + +func (SubnetRouteTableAssociationResource) destroy(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) error { + parsedId, err := parse.SubnetID(state.Attributes["subnet_id"]) + if err != nil { + return err + } + + resourceGroup := parsedId.ResourceGroup + virtualNetworkName := parsedId.VirtualNetworkName + subnetName := parsedId.Name + + read, err := client.Network.SubnetsClient.Get(ctx, resourceGroup, virtualNetworkName, subnetName, "") + if err != nil { + if !utils.ResponseWasNotFound(read.Response) { + return fmt.Errorf("retrieving Subnet %q (Network %q / Resource Group %q): %+v", subnetName, virtualNetworkName, resourceGroup, err) + } + } + + read.SubnetPropertiesFormat.RouteTable = nil + + future, err := client.Network.SubnetsClient.CreateOrUpdate(ctx, resourceGroup, virtualNetworkName, subnetName, read) + if err != nil { + return fmt.Errorf("updating Subnet %q (Network %q / Resource Group %q): %+v", subnetName, virtualNetworkName, resourceGroup, err) + } + if err = future.WaitForCompletionRef(ctx, client.Network.SubnetsClient.Client); err != nil { + return fmt.Errorf("waiting for completion of Subnet %q (Network %q / Resource Group %q): %+v", subnetName, virtualNetworkName, resourceGroup, err) + } + + return nil +} + +func (r SubnetRouteTableAssociationResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurestack_subnet" "test" { + name = "internal" + resource_group_name = "${azurestack_resource_group.test.name}" + virtual_network_name = "${azurestack_virtual_network.test.name}" + address_prefix = "10.0.2.0/24" +} + +resource "azurestack_subnet_route_table_association" "test" { + subnet_id = azurestack_subnet.test.id + route_table_id = azurestack_route_table.test.id +} +`, r.template(data)) +} + +func (r SubnetRouteTableAssociationResource) requiresImport(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurestack_subnet_route_table_association" "import" { + subnet_id = azurestack_subnet_route_table_association.test.subnet_id + route_table_id = azurestack_subnet_route_table_association.test.route_table_id +} +`, r.basic(data)) +} + +func (r SubnetRouteTableAssociationResource) updateSubnet(data acceptance.TestData) string { + return fmt.Sprintf(` +%s +resource "azurestack_subnet" "test" { + name = "internal" + resource_group_name = azurestack_resource_group.test.name + virtual_network_name = azurestack_virtual_network.test.name + address_prefix = "10.0.2.0/24" +} + +resource "azurestack_subnet_route_table_association" "test" { + subnet_id = azurestack_subnet.test.id + route_table_id = azurestack_route_table.test.id +} +`, r.template(data)) +} + +func (SubnetRouteTableAssociationResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurestack" { + features {} +} + +resource "azurestack_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurestack_virtual_network" "test" { + name = "acctestvirtnet%d" + address_space = ["10.0.0.0/16"] + location = azurestack_resource_group.test.location + resource_group_name = azurestack_resource_group.test.name +} + +resource "azurestack_route_table" "test" { + name = "acctest-%d" + location = azurestack_resource_group.test.location + resource_group_name = azurestack_resource_group.test.name + + route { + name = "first" + address_prefix = "10.100.0.0/14" + next_hop_type = "VirtualAppliance" + next_hop_in_ip_address = "10.10.1.1" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) +} diff --git a/website/docs/r/subnet_route_table_association.html.markdown b/website/docs/r/subnet_route_table_association.html.markdown new file mode 100644 index 000000000..7ce55fcd6 --- /dev/null +++ b/website/docs/r/subnet_route_table_association.html.markdown @@ -0,0 +1,84 @@ +--- +subcategory: "Network" +layout: "azurestack" +page_title: "Azure Resource Manager: azurestack_subnet_route_table_association" +description: |- + Associates a [Route Table](route_table.html) with a [Subnet](subnet.html) within a [Virtual Network](virtual_network.html). + +--- + +# azurestack_subnet_route_table_association + +Associates a [Route Table](route_table.html) with a [Subnet](subnet.html) within a [Virtual Network](virtual_network.html). + +## Example Usage + +```hcl +resource "azurestack_resource_group" "example" { + name = "example-resources" + location = "ash" +} + +resource "azurestack_virtual_network" "example" { + name = "example-network" + address_space = ["10.0.0.0/16"] + location = azurestack_resource_group.example.location + resource_group_name = azurestack_resource_group.example.name +} + +resource "azurestack_subnet" "example" { + name = "frontend" + resource_group_name = azurestack_resource_group.example.name + virtual_network_name = azurestack_virtual_network.example.name + address_prefix = "10.0.2.0/24" +} + +resource "azurestack_route_table" "example" { + name = "example-routetable" + location = azurestack_resource_group.example.location + resource_group_name = azurestack_resource_group.example.name + + route { + name = "example" + address_prefix = "10.100.0.0/14" + next_hop_type = "VirtualAppliance" + next_hop_in_ip_address = "10.10.1.1" + } +} + +resource "azurestack_subnet_route_table_association" "example" { + subnet_id = azurestack_subnet.example.id + route_table_id = azurestack_route_table.example.id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `route_table_id` - (Required) The ID of the Route Table which should be associated with the Subnet. Changing this forces a new resource to be created. + +* `subnet_id` - (Required) The ID of the Subnet. Changing this forces a new resource to be created. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Subnet. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Subnet Route Table Association. +* `update` - (Defaults to 30 minutes) Used when updating the Subnet Route Table Association. +* `read` - (Defaults to 5 minutes) Used when retrieving the Subnet Route Table Association. +* `delete` - (Defaults to 30 minutes) Used when deleting the Subnet Route Table Association. + +## Import + +Subnet Route Table Associations can be imported using the `resource id` of the Subnet, e.g. + +```shell +terraform import azurestack_subnet_route_table_association.association1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1/providers/Microsoft.Network/virtualNetworks/myvnet1/subnets/mysubnet1 +```