github.com/tarrant/terraform@v0.3.8-0.20150402012457-f68c9eee638e/builtin/providers/aws/resource_aws_route_table.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"time"
     8  
     9  	"github.com/hashicorp/aws-sdk-go/aws"
    10  	"github.com/hashicorp/aws-sdk-go/gen/ec2"
    11  	"github.com/hashicorp/terraform/helper/hashcode"
    12  	"github.com/hashicorp/terraform/helper/resource"
    13  	"github.com/hashicorp/terraform/helper/schema"
    14  )
    15  
    16  func resourceAwsRouteTable() *schema.Resource {
    17  	return &schema.Resource{
    18  		Create: resourceAwsRouteTableCreate,
    19  		Read:   resourceAwsRouteTableRead,
    20  		Update: resourceAwsRouteTableUpdate,
    21  		Delete: resourceAwsRouteTableDelete,
    22  
    23  		Schema: map[string]*schema.Schema{
    24  			"vpc_id": &schema.Schema{
    25  				Type:     schema.TypeString,
    26  				Required: true,
    27  				ForceNew: true,
    28  			},
    29  
    30  			"tags": tagsSchema(),
    31  
    32  			"route": &schema.Schema{
    33  				Type:     schema.TypeSet,
    34  				Optional: true,
    35  				Elem: &schema.Resource{
    36  					Schema: map[string]*schema.Schema{
    37  						"cidr_block": &schema.Schema{
    38  							Type:     schema.TypeString,
    39  							Required: true,
    40  						},
    41  
    42  						"gateway_id": &schema.Schema{
    43  							Type:     schema.TypeString,
    44  							Optional: true,
    45  						},
    46  
    47  						"instance_id": &schema.Schema{
    48  							Type:     schema.TypeString,
    49  							Optional: true,
    50  						},
    51  
    52  						"vpc_peering_connection_id": &schema.Schema{
    53  							Type:     schema.TypeString,
    54  							Optional: true,
    55  						},
    56  					},
    57  				},
    58  				Set: resourceAwsRouteTableHash,
    59  			},
    60  		},
    61  	}
    62  }
    63  
    64  func resourceAwsRouteTableCreate(d *schema.ResourceData, meta interface{}) error {
    65  	ec2conn := meta.(*AWSClient).ec2conn
    66  
    67  	// Create the routing table
    68  	createOpts := &ec2.CreateRouteTableRequest{
    69  		VPCID: aws.String(d.Get("vpc_id").(string)),
    70  	}
    71  	log.Printf("[DEBUG] RouteTable create config: %#v", createOpts)
    72  
    73  	resp, err := ec2conn.CreateRouteTable(createOpts)
    74  	if err != nil {
    75  		return fmt.Errorf("Error creating route table: %s", err)
    76  	}
    77  
    78  	// Get the ID and store it
    79  	rt := resp.RouteTable
    80  	d.SetId(*rt.RouteTableID)
    81  	log.Printf("[INFO] Route Table ID: %s", d.Id())
    82  
    83  	// Wait for the route table to become available
    84  	log.Printf(
    85  		"[DEBUG] Waiting for route table (%s) to become available",
    86  		d.Id())
    87  	stateConf := &resource.StateChangeConf{
    88  		Pending: []string{"pending"},
    89  		Target:  "ready",
    90  		Refresh: resourceAwsRouteTableStateRefreshFunc(ec2conn, d.Id()),
    91  		Timeout: 1 * time.Minute,
    92  	}
    93  	if _, err := stateConf.WaitForState(); err != nil {
    94  		return fmt.Errorf(
    95  			"Error waiting for route table (%s) to become available: %s",
    96  			d.Id(), err)
    97  	}
    98  
    99  	return resourceAwsRouteTableUpdate(d, meta)
   100  }
   101  
   102  func resourceAwsRouteTableRead(d *schema.ResourceData, meta interface{}) error {
   103  	ec2conn := meta.(*AWSClient).ec2conn
   104  
   105  	rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(ec2conn, d.Id())()
   106  	if err != nil {
   107  		return err
   108  	}
   109  	if rtRaw == nil {
   110  		d.SetId("")
   111  		return nil
   112  	}
   113  
   114  	rt := rtRaw.(*ec2.RouteTable)
   115  	d.Set("vpc_id", rt.VPCID)
   116  
   117  	// Create an empty schema.Set to hold all routes
   118  	route := &schema.Set{F: resourceAwsRouteTableHash}
   119  
   120  	// Loop through the routes and add them to the set
   121  	for _, r := range rt.Routes {
   122  		if r.GatewayID != nil && *r.GatewayID == "local" {
   123  			continue
   124  		}
   125  
   126  		if r.Origin != nil && *r.Origin == "EnableVgwRoutePropagation" {
   127  			continue
   128  		}
   129  
   130  		m := make(map[string]interface{})
   131  
   132  		if r.DestinationCIDRBlock != nil {
   133  			m["cidr_block"] = *r.DestinationCIDRBlock
   134  		}
   135  		if r.GatewayID != nil {
   136  			m["gateway_id"] = *r.GatewayID
   137  		}
   138  		if r.InstanceID != nil {
   139  			m["instance_id"] = *r.InstanceID
   140  		}
   141  		if r.VPCPeeringConnectionID != nil {
   142  			m["vpc_peering_connection_id"] = *r.VPCPeeringConnectionID
   143  		}
   144  
   145  		route.Add(m)
   146  	}
   147  	d.Set("route", route)
   148  
   149  	// Tags
   150  	d.Set("tags", tagsToMap(rt.Tags))
   151  
   152  	return nil
   153  }
   154  
   155  func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error {
   156  	ec2conn := meta.(*AWSClient).ec2conn
   157  
   158  	// Check if the route set as a whole has changed
   159  	if d.HasChange("route") {
   160  		o, n := d.GetChange("route")
   161  		ors := o.(*schema.Set).Difference(n.(*schema.Set))
   162  		nrs := n.(*schema.Set).Difference(o.(*schema.Set))
   163  
   164  		// Now first loop through all the old routes and delete any obsolete ones
   165  		for _, route := range ors.List() {
   166  			m := route.(map[string]interface{})
   167  
   168  			// Delete the route as it no longer exists in the config
   169  			log.Printf(
   170  				"[INFO] Deleting route from %s: %s",
   171  				d.Id(), m["cidr_block"].(string))
   172  			err := ec2conn.DeleteRoute(&ec2.DeleteRouteRequest{
   173  				RouteTableID:         aws.String(d.Id()),
   174  				DestinationCIDRBlock: aws.String(m["cidr_block"].(string)),
   175  			})
   176  			if err != nil {
   177  				return err
   178  			}
   179  		}
   180  
   181  		// Make sure we save the state of the currently configured rules
   182  		routes := o.(*schema.Set).Intersection(n.(*schema.Set))
   183  		d.Set("route", routes)
   184  
   185  		// Then loop through al the newly configured routes and create them
   186  		for _, route := range nrs.List() {
   187  			m := route.(map[string]interface{})
   188  
   189  			opts := ec2.CreateRouteRequest{
   190  				RouteTableID:           aws.String(d.Id()),
   191  				DestinationCIDRBlock:   aws.String(m["cidr_block"].(string)),
   192  				GatewayID:              aws.String(m["gateway_id"].(string)),
   193  				InstanceID:             aws.String(m["instance_id"].(string)),
   194  				VPCPeeringConnectionID: aws.String(m["vpc_peering_connection_id"].(string)),
   195  			}
   196  
   197  			log.Printf("[INFO] Creating route for %s: %#v", d.Id(), opts)
   198  			if err := ec2conn.CreateRoute(&opts); err != nil {
   199  				return err
   200  			}
   201  
   202  			routes.Add(route)
   203  			d.Set("route", routes)
   204  		}
   205  	}
   206  
   207  	if err := setTags(ec2conn, d); err != nil {
   208  		return err
   209  	} else {
   210  		d.SetPartial("tags")
   211  	}
   212  
   213  	return resourceAwsRouteTableRead(d, meta)
   214  }
   215  
   216  func resourceAwsRouteTableDelete(d *schema.ResourceData, meta interface{}) error {
   217  	ec2conn := meta.(*AWSClient).ec2conn
   218  
   219  	// First request the routing table since we'll have to disassociate
   220  	// all the subnets first.
   221  	rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(ec2conn, d.Id())()
   222  	if err != nil {
   223  		return err
   224  	}
   225  	if rtRaw == nil {
   226  		return nil
   227  	}
   228  	rt := rtRaw.(*ec2.RouteTable)
   229  
   230  	// Do all the disassociations
   231  	for _, a := range rt.Associations {
   232  		log.Printf("[INFO] Disassociating association: %s", *a.RouteTableAssociationID)
   233  		err := ec2conn.DisassociateRouteTable(&ec2.DisassociateRouteTableRequest{
   234  			AssociationID: a.RouteTableAssociationID,
   235  		})
   236  		if err != nil {
   237  			return err
   238  		}
   239  	}
   240  
   241  	// Delete the route table
   242  	log.Printf("[INFO] Deleting Route Table: %s", d.Id())
   243  	err = ec2conn.DeleteRouteTable(&ec2.DeleteRouteTableRequest{
   244  		RouteTableID: aws.String(d.Id()),
   245  	})
   246  	if err != nil {
   247  		ec2err, ok := err.(aws.APIError)
   248  		if ok && ec2err.Code == "InvalidRouteTableID.NotFound" {
   249  			return nil
   250  		}
   251  
   252  		return fmt.Errorf("Error deleting route table: %s", err)
   253  	}
   254  
   255  	// Wait for the route table to really destroy
   256  	log.Printf(
   257  		"[DEBUG] Waiting for route table (%s) to become destroyed",
   258  		d.Id())
   259  
   260  	stateConf := &resource.StateChangeConf{
   261  		Pending: []string{"ready"},
   262  		Target:  "",
   263  		Refresh: resourceAwsRouteTableStateRefreshFunc(ec2conn, d.Id()),
   264  		Timeout: 1 * time.Minute,
   265  	}
   266  	if _, err := stateConf.WaitForState(); err != nil {
   267  		return fmt.Errorf(
   268  			"Error waiting for route table (%s) to become destroyed: %s",
   269  			d.Id(), err)
   270  	}
   271  
   272  	return nil
   273  }
   274  
   275  func resourceAwsRouteTableHash(v interface{}) int {
   276  	var buf bytes.Buffer
   277  	m := v.(map[string]interface{})
   278  	buf.WriteString(fmt.Sprintf("%s-", m["cidr_block"].(string)))
   279  
   280  	if v, ok := m["gateway_id"]; ok {
   281  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   282  	}
   283  
   284  	if v, ok := m["instance_id"]; ok {
   285  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   286  	}
   287  
   288  	if v, ok := m["vpc_peering_connection_id"]; ok {
   289  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   290  	}
   291  
   292  	return hashcode.String(buf.String())
   293  }
   294  
   295  // resourceAwsRouteTableStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   296  // a RouteTable.
   297  func resourceAwsRouteTableStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
   298  	return func() (interface{}, string, error) {
   299  		resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesRequest{
   300  			RouteTableIDs: []string{id},
   301  		})
   302  		if err != nil {
   303  			if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidRouteTableID.NotFound" {
   304  				resp = nil
   305  			} else {
   306  				log.Printf("Error on RouteTableStateRefresh: %s", err)
   307  				return nil, "", err
   308  			}
   309  		}
   310  
   311  		if resp == nil {
   312  			// Sometimes AWS just has consistency issues and doesn't see
   313  			// our instance yet. Return an empty state.
   314  			return nil, "", nil
   315  		}
   316  
   317  		rt := &resp.RouteTables[0]
   318  		return rt, "ready", nil
   319  	}
   320  }