Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provider/aws: aws_main_route_table_association #918

Merged
merged 2 commits into from
Feb 3, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 21 additions & 20 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,26 +45,27 @@ func Provider() terraform.ResourceProvider {
},

ResourcesMap: map[string]*schema.Resource{
"aws_autoscaling_group": resourceAwsAutoscalingGroup(),
"aws_db_instance": resourceAwsDbInstance(),
"aws_db_parameter_group": resourceAwsDbParameterGroup(),
"aws_db_security_group": resourceAwsDbSecurityGroup(),
"aws_db_subnet_group": resourceAwsDbSubnetGroup(),
"aws_eip": resourceAwsEip(),
"aws_elb": resourceAwsElb(),
"aws_instance": resourceAwsInstance(),
"aws_internet_gateway": resourceAwsInternetGateway(),
"aws_key_pair": resourceAwsKeyPair(),
"aws_launch_configuration": resourceAwsLaunchConfiguration(),
"aws_network_acl": resourceAwsNetworkAcl(),
"aws_route53_record": resourceAwsRoute53Record(),
"aws_route53_zone": resourceAwsRoute53Zone(),
"aws_route_table": resourceAwsRouteTable(),
"aws_route_table_association": resourceAwsRouteTableAssociation(),
"aws_s3_bucket": resourceAwsS3Bucket(),
"aws_security_group": resourceAwsSecurityGroup(),
"aws_subnet": resourceAwsSubnet(),
"aws_vpc": resourceAwsVpc(),
"aws_autoscaling_group": resourceAwsAutoscalingGroup(),
"aws_db_instance": resourceAwsDbInstance(),
"aws_db_parameter_group": resourceAwsDbParameterGroup(),
"aws_db_security_group": resourceAwsDbSecurityGroup(),
"aws_db_subnet_group": resourceAwsDbSubnetGroup(),
"aws_eip": resourceAwsEip(),
"aws_elb": resourceAwsElb(),
"aws_instance": resourceAwsInstance(),
"aws_internet_gateway": resourceAwsInternetGateway(),
"aws_key_pair": resourceAwsKeyPair(),
"aws_launch_configuration": resourceAwsLaunchConfiguration(),
"aws_main_route_table_association": resourceAwsMainRouteTableAssociation(),
"aws_network_acl": resourceAwsNetworkAcl(),
"aws_route53_record": resourceAwsRoute53Record(),
"aws_route53_zone": resourceAwsRoute53Zone(),
"aws_route_table": resourceAwsRouteTable(),
"aws_route_table_association": resourceAwsRouteTableAssociation(),
"aws_s3_bucket": resourceAwsS3Bucket(),
"aws_security_group": resourceAwsSecurityGroup(),
"aws_subnet": resourceAwsSubnet(),
"aws_vpc": resourceAwsVpc(),
},

ConfigureFunc: providerConfigure,
Expand Down
155 changes: 155 additions & 0 deletions builtin/providers/aws/resource_aws_main_route_table_association.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package aws

import (
"fmt"
"log"

"github.com/hashicorp/terraform/helper/schema"
"github.com/mitchellh/goamz/ec2"
)

func resourceAwsMainRouteTableAssociation() *schema.Resource {
return &schema.Resource{
Create: resourceAwsMainRouteTableAssociationCreate,
Read: resourceAwsMainRouteTableAssociationRead,
Update: resourceAwsMainRouteTableAssociationUpdate,
Delete: resourceAwsMainRouteTableAssociationDelete,

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

"route_table_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
},

// We use this field to record the main route table that is automatically
// created when the VPC is created. We need this to be able to "destroy"
// our main route table association, which we do by returning this route
// table to its original place as the Main Route Table for the VPC.
"original_route_table_id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}

func resourceAwsMainRouteTableAssociationCreate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn
vpcId := d.Get("vpc_id").(string)
routeTableId := d.Get("route_table_id").(string)

log.Printf("[INFO] Creating main route table association: %s => %s", vpcId, routeTableId)

mainAssociation, err := findMainRouteTableAssociation(ec2conn, vpcId)
if err != nil {
return err
}

resp, err := ec2conn.ReassociateRouteTable(
mainAssociation.AssociationId,
routeTableId,
)
if err != nil {
return err
}

d.Set("original_route_table_id", mainAssociation.RouteTableId)
d.SetId(resp.AssociationId)
log.Printf("[INFO] New main route table association ID: %s", d.Id())

return nil
}

func resourceAwsMainRouteTableAssociationRead(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn

mainAssociation, err := findMainRouteTableAssociation(
ec2conn,
d.Get("vpc_id").(string))
if err != nil {
return err
}

if mainAssociation.AssociationId != d.Id() {
// It seems it doesn't exist anymore, so clear the ID
d.SetId("")
}

return nil
}

// Update is almost exactly like Create, except we want to retain the
// original_route_table_id - this needs to stay recorded as the AWS-created
// table from VPC creation.
func resourceAwsMainRouteTableAssociationUpdate(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn
vpcId := d.Get("vpc_id").(string)
routeTableId := d.Get("route_table_id").(string)

log.Printf("[INFO] Updating main route table association: %s => %s", vpcId, routeTableId)

resp, err := ec2conn.ReassociateRouteTable(d.Id(), routeTableId)
if err != nil {
return err
}

d.SetId(resp.AssociationId)
log.Printf("[INFO] New main route table association ID: %s", d.Id())

return nil
}

func resourceAwsMainRouteTableAssociationDelete(d *schema.ResourceData, meta interface{}) error {
ec2conn := meta.(*AWSClient).ec2conn
vpcId := d.Get("vpc_id").(string)
originalRouteTableId := d.Get("original_route_table_id").(string)

log.Printf("[INFO] Deleting main route table association by resetting Main Route Table for VPC: %s to its original Route Table: %s",
vpcId,
originalRouteTableId)

resp, err := ec2conn.ReassociateRouteTable(d.Id(), originalRouteTableId)
if err != nil {
return err
}

log.Printf("[INFO] Resulting Association ID: %s", resp.AssociationId)

return nil
}

func findMainRouteTableAssociation(ec2conn *ec2.EC2, vpcId string) (*ec2.RouteTableAssociation, error) {
mainRouteTable, err := findMainRouteTable(ec2conn, vpcId)
if err != nil {
return nil, err
}

for _, a := range mainRouteTable.Associations {
if a.Main {
return &a, nil
}
}
return nil, fmt.Errorf("Could not find main routing table association for VPC: %s", vpcId)
}

func findMainRouteTable(ec2conn *ec2.EC2, vpcId string) (*ec2.RouteTable, error) {
filter := ec2.NewFilter()
filter.Add("association.main", "true")
filter.Add("vpc-id", vpcId)
routeResp, err := ec2conn.DescribeRouteTables(nil, filter)
if err != nil {
return nil, err
} else if len(routeResp.RouteTables) != 1 {
return nil, fmt.Errorf(
"Expected to find a single main routing table for VPC: %s, but found %d",
vpcId,
len(routeResp.RouteTables))
}

return &routeResp.RouteTables[0], nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package aws

import (
"fmt"
"testing"

"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSMainRouteTableAssociation(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckMainRouteTableAssociationDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccMainRouteTableAssociationConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckMainRouteTableAssociation(
"aws_main_route_table_association.foo",
"aws_vpc.foo",
"aws_route_table.foo",
),
),
},
resource.TestStep{
Config: testAccMainRouteTableAssociationConfigUpdate,
Check: resource.ComposeTestCheckFunc(
testAccCheckMainRouteTableAssociation(
"aws_main_route_table_association.foo",
"aws_vpc.foo",
"aws_route_table.bar",
),
),
},
},
})
}

func testAccCheckMainRouteTableAssociationDestroy(s *terraform.State) error {
if len(s.RootModule().Resources) > 0 {
return fmt.Errorf("Expected all resources to be gone, but found: %#v", s.RootModule().Resources)
}

return nil
}

func testAccCheckMainRouteTableAssociation(
mainRouteTableAssociationResource string,
vpcResource string,
routeTableResource string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[mainRouteTableAssociationResource]
if !ok {
return fmt.Errorf("Not found: %s", mainRouteTableAssociationResource)
}

if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}

vpc, ok := s.RootModule().Resources[vpcResource]
if !ok {
return fmt.Errorf("Not found: %s", vpcResource)
}

conn := testAccProvider.Meta().(*AWSClient).ec2conn
mainAssociation, err := findMainRouteTableAssociation(conn, vpc.Primary.ID)
if err != nil {
return err
}

if mainAssociation.AssociationId != rs.Primary.ID {
return fmt.Errorf("Found wrong main association: %s",
mainAssociation.AssociationId)
}

return nil
}
}

const testAccMainRouteTableAssociationConfig = `
resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16"
}

resource "aws_subnet" "foo" {
vpc_id = "${aws_vpc.foo.id}"
cidr_block = "10.1.1.0/24"
}

resource "aws_internet_gateway" "foo" {
vpc_id = "${aws_vpc.foo.id}"
}

resource "aws_route_table" "foo" {
vpc_id = "${aws_vpc.foo.id}"
route {
cidr_block = "10.0.0.0/8"
gateway_id = "${aws_internet_gateway.foo.id}"
}
}

resource "aws_main_route_table_association" "foo" {
vpc_id = "${aws_vpc.foo.id}"
route_table_id = "${aws_route_table.foo.id}"
}
`

const testAccMainRouteTableAssociationConfigUpdate = `
resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16"
}

resource "aws_subnet" "foo" {
vpc_id = "${aws_vpc.foo.id}"
cidr_block = "10.1.1.0/24"
}

resource "aws_internet_gateway" "foo" {
vpc_id = "${aws_vpc.foo.id}"
}

// Need to keep the old route table around when we update the
// main_route_table_association, otherwise Terraform will try to destroy the
// route table too early, and will fail because it's still the main one
resource "aws_route_table" "foo" {
vpc_id = "${aws_vpc.foo.id}"
route {
cidr_block = "10.0.0.0/8"
gateway_id = "${aws_internet_gateway.foo.id}"
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mitchellh ^^ this limitation of the current terraform graph is worth noting. AFAICT there's currently no way of expressing the required order of operations to make this possible

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this relates back to that same issue of implicit deps...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep exactly. Will be an interesting problem to solve! 💭


resource "aws_route_table" "bar" {
vpc_id = "${aws_vpc.foo.id}"
route {
cidr_block = "10.0.0.0/8"
gateway_id = "${aws_internet_gateway.foo.id}"
}
}

resource "aws_main_route_table_association" "foo" {
vpc_id = "${aws_vpc.foo.id}"
route_table_id = "${aws_route_table.bar.id}"
}
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
layout: "aws"
page_title: "AWS: aws_main_route_table_association"
sidebar_current: "docs-aws-resource-main-route-table-assoc"
description: |-
Provides a resource for managing the main routing table of a VPC.
---

# aws\_main\_route\_table\_<wbr>association

Provides a resource for managing the main routing table of a VPC.

## Example Usage

```
resource "aws_main_route_table_association" "a" {
vpc_id = "${aws_vpc.foo.id}"
route_table_id = "${aws_route_table.bar.id}"
}
```

## Argument Reference

The following arguments are supported:

* `vpc_id` - (Required) The ID of the VPC whose main route table should be set
* `route_table_id` - (Required) The ID of the Route Table to set as the new
main route table for the target VPC

## Attributes Reference

The following attributes are exported:

* `id` - The ID of the Route Table Association
* `original_route_table_id` - Used internally, see __Notes__ below

## Notes

On VPC creation, the AWS API always creates an initial Main Route Table. This
resource records the ID of that Route Table under `original_route_table_id`.
The "Delete" action for a `main_route_table_association` consists of resetting
this original table as the Main Route Table for the VPC. You'll see this
additional Route Table in the AWS console; it must remain intact in order for
the `main_route_table_association` delete to work properly.
Loading