Skip to content

Commit

Permalink
Merge pull request #6999 from YakDriver/reassociate_subnet
Browse files Browse the repository at this point in the history
Allow forced replacement of route table associated with subnet
  • Loading branch information
bflad authored Jul 31, 2019
2 parents 674f7bb + 07a1091 commit e2cadb3
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 16 deletions.
61 changes: 55 additions & 6 deletions aws/resource_aws_route_table_association.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package aws
import (
"fmt"
"log"
"strings"
"time"

"github.com/aws/aws-sdk-go/aws"
Expand All @@ -18,12 +19,14 @@ func resourceAwsRouteTableAssociation() *schema.Resource {
Read: resourceAwsRouteTableAssociationRead,
Update: resourceAwsRouteTableAssociationUpdate,
Delete: resourceAwsRouteTableAssociationDelete,
Importer: &schema.ResourceImporter{
State: resourceAwsRouteTableAssociationImport,
},

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

"route_table_id": {
Expand All @@ -47,10 +50,9 @@ func resourceAwsRouteTableAssociationCreate(d *schema.ResourceData, meta interfa
SubnetId: aws.String(d.Get("subnet_id").(string)),
}

var resp *ec2.AssociateRouteTableOutput
var err error
err = resource.Retry(5*time.Minute, func() *resource.RetryError {
resp, err = conn.AssociateRouteTable(&associationOpts)
var associationID string
err := resource.Retry(5*time.Minute, func() *resource.RetryError {
resp, err := conn.AssociateRouteTable(&associationOpts)
if err != nil {
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == "InvalidRouteTableID.NotFound" {
Expand All @@ -59,14 +61,15 @@ func resourceAwsRouteTableAssociationCreate(d *schema.ResourceData, meta interfa
}
return resource.NonRetryableError(err)
}
associationID = *resp.AssociationId
return nil
})
if err != nil {
return err
}

// Set the ID and return
d.SetId(*resp.AssociationId)
d.SetId(associationID)
log.Printf("[INFO] Association ID: %s", d.Id())

return nil
Expand Down Expand Up @@ -153,3 +156,49 @@ func resourceAwsRouteTableAssociationDelete(d *schema.ResourceData, meta interfa

return nil
}

func resourceAwsRouteTableAssociationImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
parts := strings.Split(d.Id(), "/")
if len(parts) != 2 {
return []*schema.ResourceData{}, fmt.Errorf("Wrong format for import: %s. Use 'subnet ID/route table ID'", d.Id())
}

subnetID := parts[0]
routeTableID := parts[1]

log.Printf("[DEBUG] Importing route table association, subnet: %s, route table: %s", subnetID, routeTableID)

conn := meta.(*AWSClient).ec2conn

input := &ec2.DescribeRouteTablesInput{}
input.Filters = buildEC2AttributeFilterList(
map[string]string{
"association.subnet-id": subnetID,
"association.route-table-id": routeTableID,
},
)

output, err := conn.DescribeRouteTables(input)
if err != nil || len(output.RouteTables) == 0 {
return nil, fmt.Errorf("Error finding route table: %v", err)
}

rt := output.RouteTables[0]

var associationID string
for _, a := range rt.Associations {
if aws.StringValue(a.SubnetId) == subnetID {
associationID = aws.StringValue(a.RouteTableAssociationId)
break
}
}
if associationID == "" {
return nil, fmt.Errorf("Error finding route table, ID: %v", *rt.RouteTableId)
}

d.SetId(associationID)
d.Set("subnet_id", subnetID)
d.Set("route_table_id", routeTableID)

return []*schema.ResourceData{d}, nil
}
117 changes: 107 additions & 10 deletions aws/resource_aws_route_table_association_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
func TestAccAWSRouteTableAssociation_basic(t *testing.T) {
var v, v2 ec2.RouteTable

resourceName := "aws_route_table_association.foo"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Expand All @@ -23,15 +25,53 @@ func TestAccAWSRouteTableAssociation_basic(t *testing.T) {
Config: testAccRouteTableAssociationConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckRouteTableAssociationExists(
"aws_route_table_association.foo", &v),
resourceName, &v),
),
},

{
Config: testAccRouteTableAssociationConfigChange,
Check: resource.ComposeTestCheckFunc(
testAccCheckRouteTableAssociationExists(
"aws_route_table_association.foo", &v2),
resourceName, &v2),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateIdFunc: testAccAWSRouteTabAssocImportStateIdFunc(resourceName),
ImportStateVerify: true,
},
},
})
}

func TestAccAWSRouteTableAssociation_replace(t *testing.T) {
var v, v2 ec2.RouteTable
resourceName := "aws_route_table_association.foo"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckRouteTableAssociationDestroy,
Steps: []resource.TestStep{
{
Config: testAccRouteTableAssociationConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckRouteTableAssociationExists(
resourceName, &v),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateIdFunc: testAccAWSRouteTabAssocImportStateIdFunc(resourceName),
ImportStateVerify: true,
},
{
Config: testAccRouteTableAssociationConfigReplace,
Check: resource.ComposeTestCheckFunc(
testAccCheckRouteTableAssociationExists(
resourceName, &v2),
),
},
},
Expand Down Expand Up @@ -105,27 +145,37 @@ func testAccCheckRouteTableAssociationExists(n string, v *ec2.RouteTable) resour
}
}

func testAccAWSRouteTabAssocImportStateIdFunc(resourceName string) resource.ImportStateIdFunc {
return func(s *terraform.State) (string, error) {
rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return "", fmt.Errorf("not found: %s", resourceName)
}

return fmt.Sprintf("%s/%s", rs.Primary.Attributes["subnet_id"], rs.Primary.Attributes["route_table_id"]), nil
}
}

const testAccRouteTableAssociationConfig = `
resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16"
tags = {
Name = "terraform-testacc-route-table-association"
Name = "tf-acc-route-table-assoc"
}
}
resource "aws_subnet" "foo" {
vpc_id = "${aws_vpc.foo.id}"
cidr_block = "10.1.1.0/24"
tags = {
Name = "tf-acc-route-table-association"
Name = "tf-acc-route-table-assoc"
}
}
resource "aws_internet_gateway" "foo" {
vpc_id = "${aws_vpc.foo.id}"
tags = {
Name = "terraform-testacc-route-table-association"
Name = "tf-acc-route-table-assoc"
}
}
Expand All @@ -135,6 +185,9 @@ resource "aws_route_table" "foo" {
cidr_block = "10.0.0.0/8"
gateway_id = "${aws_internet_gateway.foo.id}"
}
tags = {
Name = "tf-acc-route-table-assoc"
}
}
resource "aws_route_table_association" "foo" {
Expand All @@ -147,23 +200,23 @@ const testAccRouteTableAssociationConfigChange = `
resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16"
tags = {
Name = "terraform-testacc-route-table-association"
Name = "tf-acc-route-table-assoc"
}
}
resource "aws_subnet" "foo" {
vpc_id = "${aws_vpc.foo.id}"
cidr_block = "10.1.1.0/24"
tags = {
Name = "tf-acc-route-table-association"
Name = "tf-acc-route-table-assoc"
}
}
resource "aws_internet_gateway" "foo" {
vpc_id = "${aws_vpc.foo.id}"
tags = {
Name = "terraform-testacc-route-table-association"
Name = "tf-acc-route-table-assoc"
}
}
Expand All @@ -173,6 +226,50 @@ resource "aws_route_table" "bar" {
cidr_block = "10.0.0.0/8"
gateway_id = "${aws_internet_gateway.foo.id}"
}
tags = {
Name = "tf-acc-route-change-table-assoc"
}
}
resource "aws_route_table_association" "foo" {
route_table_id = "${aws_route_table.bar.id}"
subnet_id = "${aws_subnet.foo.id}"
}
`

const testAccRouteTableAssociationConfigReplace = `
resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16"
tags = {
Name = "tf-acc-route-table-assoc"
}
}
resource "aws_subnet" "foo" {
vpc_id = "${aws_vpc.foo.id}"
cidr_block = "10.1.1.0/24"
tags = {
Name = "tf-acc-route-table-assoc"
}
}
resource "aws_internet_gateway" "foo" {
vpc_id = "${aws_vpc.foo.id}"
tags = {
Name = "tf-acc-route-table-assoc"
}
}
resource "aws_route_table" "bar" {
vpc_id = "${aws_vpc.foo.id}"
route {
cidr_block = "10.0.0.0/16"
gateway_id = "${aws_internet_gateway.foo.id}"
}
tags = {
Name = "tf-acc-replace-route-table-assoc"
}
}
resource "aws_route_table_association" "foo" {
Expand Down
14 changes: 14 additions & 0 deletions website/docs/r/route_table_association.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,17 @@ In addition to all arguments above, the following attributes are exported:

* `id` - The ID of the association

## Import

~> **NOTE:** Attempting to associate a route table with a subnet, where either
is already associated, will result in an error (e.g.,
`Resource.AlreadyAssociated: the specified association for route table
rtb-4176657279 conflicts with an existing association`) unless you first
import the original association.

Route table associations can be imported using the subnet and route table IDs.
For example, use this command:

```
$ terraform import aws_route_table_association.assoc subnet-6777656e646f6c796e/rtb-656c65616e6f72
```

0 comments on commit e2cadb3

Please sign in to comment.