github.com/bendemaree/terraform@v0.5.4-0.20150613200311-f50d97d6eee6/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/aws/aws-sdk-go/aws"
    10  	"github.com/aws/aws-sdk-go/aws/awserr"
    11  	"github.com/aws/aws-sdk-go/service/ec2"
    12  	"github.com/hashicorp/terraform/helper/hashcode"
    13  	"github.com/hashicorp/terraform/helper/resource"
    14  	"github.com/hashicorp/terraform/helper/schema"
    15  )
    16  
    17  func resourceAwsRouteTable() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceAwsRouteTableCreate,
    20  		Read:   resourceAwsRouteTableRead,
    21  		Update: resourceAwsRouteTableUpdate,
    22  		Delete: resourceAwsRouteTableDelete,
    23  
    24  		Schema: map[string]*schema.Schema{
    25  			"vpc_id": &schema.Schema{
    26  				Type:     schema.TypeString,
    27  				Required: true,
    28  				ForceNew: true,
    29  			},
    30  
    31  			"tags": tagsSchema(),
    32  
    33  			"propagating_vgws": &schema.Schema{
    34  				Type:     schema.TypeSet,
    35  				Optional: true,
    36  				Elem:     &schema.Schema{Type: schema.TypeString},
    37  				Set: func(v interface{}) int {
    38  					return hashcode.String(v.(string))
    39  				},
    40  			},
    41  
    42  			"route": &schema.Schema{
    43  				Type:     schema.TypeSet,
    44  				Optional: true,
    45  				Elem: &schema.Resource{
    46  					Schema: map[string]*schema.Schema{
    47  						"cidr_block": &schema.Schema{
    48  							Type:     schema.TypeString,
    49  							Required: true,
    50  						},
    51  
    52  						"gateway_id": &schema.Schema{
    53  							Type:     schema.TypeString,
    54  							Optional: true,
    55  						},
    56  
    57  						"instance_id": &schema.Schema{
    58  							Type:     schema.TypeString,
    59  							Optional: true,
    60  						},
    61  
    62  						"vpc_peering_connection_id": &schema.Schema{
    63  							Type:     schema.TypeString,
    64  							Optional: true,
    65  						},
    66  
    67  						"network_interface_id": &schema.Schema{
    68  							Type:     schema.TypeString,
    69  							Optional: true,
    70  						},
    71  					},
    72  				},
    73  				Set: resourceAwsRouteTableHash,
    74  			},
    75  		},
    76  	}
    77  }
    78  
    79  func resourceAwsRouteTableCreate(d *schema.ResourceData, meta interface{}) error {
    80  	conn := meta.(*AWSClient).ec2conn
    81  
    82  	// Create the routing table
    83  	createOpts := &ec2.CreateRouteTableInput{
    84  		VPCID: aws.String(d.Get("vpc_id").(string)),
    85  	}
    86  	log.Printf("[DEBUG] RouteTable create config: %#v", createOpts)
    87  
    88  	resp, err := conn.CreateRouteTable(createOpts)
    89  	if err != nil {
    90  		return fmt.Errorf("Error creating route table: %s", err)
    91  	}
    92  
    93  	// Get the ID and store it
    94  	rt := resp.RouteTable
    95  	d.SetId(*rt.RouteTableID)
    96  	log.Printf("[INFO] Route Table ID: %s", d.Id())
    97  
    98  	// Wait for the route table to become available
    99  	log.Printf(
   100  		"[DEBUG] Waiting for route table (%s) to become available",
   101  		d.Id())
   102  	stateConf := &resource.StateChangeConf{
   103  		Pending: []string{"pending"},
   104  		Target:  "ready",
   105  		Refresh: resourceAwsRouteTableStateRefreshFunc(conn, d.Id()),
   106  		Timeout: 1 * time.Minute,
   107  	}
   108  	if _, err := stateConf.WaitForState(); err != nil {
   109  		return fmt.Errorf(
   110  			"Error waiting for route table (%s) to become available: %s",
   111  			d.Id(), err)
   112  	}
   113  
   114  	return resourceAwsRouteTableUpdate(d, meta)
   115  }
   116  
   117  func resourceAwsRouteTableRead(d *schema.ResourceData, meta interface{}) error {
   118  	conn := meta.(*AWSClient).ec2conn
   119  
   120  	rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(conn, d.Id())()
   121  	if err != nil {
   122  		return err
   123  	}
   124  	if rtRaw == nil {
   125  		d.SetId("")
   126  		return nil
   127  	}
   128  
   129  	rt := rtRaw.(*ec2.RouteTable)
   130  	d.Set("vpc_id", rt.VPCID)
   131  
   132  	propagatingVGWs := make([]string, 0, len(rt.PropagatingVGWs))
   133  	for _, vgw := range rt.PropagatingVGWs {
   134  		propagatingVGWs = append(propagatingVGWs, *vgw.GatewayID)
   135  	}
   136  	d.Set("propagating_vgws", propagatingVGWs)
   137  
   138  	// Create an empty schema.Set to hold all routes
   139  	route := &schema.Set{F: resourceAwsRouteTableHash}
   140  
   141  	// Loop through the routes and add them to the set
   142  	for _, r := range rt.Routes {
   143  		if r.GatewayID != nil && *r.GatewayID == "local" {
   144  			continue
   145  		}
   146  
   147  		if r.Origin != nil && *r.Origin == "EnableVgwRoutePropagation" {
   148  			continue
   149  		}
   150  
   151  		m := make(map[string]interface{})
   152  
   153  		if r.DestinationCIDRBlock != nil {
   154  			m["cidr_block"] = *r.DestinationCIDRBlock
   155  		}
   156  		if r.GatewayID != nil {
   157  			m["gateway_id"] = *r.GatewayID
   158  		}
   159  		if r.InstanceID != nil {
   160  			m["instance_id"] = *r.InstanceID
   161  		}
   162  		if r.VPCPeeringConnectionID != nil {
   163  			m["vpc_peering_connection_id"] = *r.VPCPeeringConnectionID
   164  		}
   165  		if r.NetworkInterfaceID != nil {
   166  			m["network_interface_id"] = *r.NetworkInterfaceID
   167  		}
   168  
   169  		route.Add(m)
   170  	}
   171  	d.Set("route", route)
   172  
   173  	// Tags
   174  	d.Set("tags", tagsToMap(rt.Tags))
   175  
   176  	return nil
   177  }
   178  
   179  func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error {
   180  	conn := meta.(*AWSClient).ec2conn
   181  
   182  	if d.HasChange("propagating_vgws") {
   183  		o, n := d.GetChange("propagating_vgws")
   184  		os := o.(*schema.Set)
   185  		ns := n.(*schema.Set)
   186  		remove := os.Difference(ns).List()
   187  		add := ns.Difference(os).List()
   188  
   189  		// Now first loop through all the old propagations and disable any obsolete ones
   190  		for _, vgw := range remove {
   191  			id := vgw.(string)
   192  
   193  			// Disable the propagation as it no longer exists in the config
   194  			log.Printf(
   195  				"[INFO] Deleting VGW propagation from %s: %s",
   196  				d.Id(), id)
   197  			_, err := conn.DisableVGWRoutePropagation(&ec2.DisableVGWRoutePropagationInput{
   198  				RouteTableID: aws.String(d.Id()),
   199  				GatewayID:    aws.String(id),
   200  			})
   201  			if err != nil {
   202  				return err
   203  			}
   204  		}
   205  
   206  		// Make sure we save the state of the currently configured rules
   207  		propagatingVGWs := os.Intersection(ns)
   208  		d.Set("propagating_vgws", propagatingVGWs)
   209  
   210  		// Then loop through all the newly configured propagations and enable them
   211  		for _, vgw := range add {
   212  			id := vgw.(string)
   213  
   214  			var err error
   215  			for i := 0; i < 5; i++ {
   216  				log.Printf("[INFO] Enabling VGW propagation for %s: %s", d.Id(), id)
   217  				_, err = conn.EnableVGWRoutePropagation(&ec2.EnableVGWRoutePropagationInput{
   218  					RouteTableID: aws.String(d.Id()),
   219  					GatewayID:    aws.String(id),
   220  				})
   221  				if err == nil {
   222  					break
   223  				}
   224  
   225  				// If we get a Gateway.NotAttached, it is usually some
   226  				// eventually consistency stuff. So we have to just wait a
   227  				// bit...
   228  				ec2err, ok := err.(awserr.Error)
   229  				if ok && ec2err.Code() == "Gateway.NotAttached" {
   230  					time.Sleep(20 * time.Second)
   231  					continue
   232  				}
   233  			}
   234  			if err != nil {
   235  				return err
   236  			}
   237  
   238  			propagatingVGWs.Add(vgw)
   239  			d.Set("propagating_vgws", propagatingVGWs)
   240  		}
   241  	}
   242  
   243  	// Check if the route set as a whole has changed
   244  	if d.HasChange("route") {
   245  		o, n := d.GetChange("route")
   246  		ors := o.(*schema.Set).Difference(n.(*schema.Set))
   247  		nrs := n.(*schema.Set).Difference(o.(*schema.Set))
   248  
   249  		// Now first loop through all the old routes and delete any obsolete ones
   250  		for _, route := range ors.List() {
   251  			m := route.(map[string]interface{})
   252  
   253  			// Delete the route as it no longer exists in the config
   254  			log.Printf(
   255  				"[INFO] Deleting route from %s: %s",
   256  				d.Id(), m["cidr_block"].(string))
   257  			_, err := conn.DeleteRoute(&ec2.DeleteRouteInput{
   258  				RouteTableID:         aws.String(d.Id()),
   259  				DestinationCIDRBlock: aws.String(m["cidr_block"].(string)),
   260  			})
   261  			if err != nil {
   262  				return err
   263  			}
   264  		}
   265  
   266  		// Make sure we save the state of the currently configured rules
   267  		routes := o.(*schema.Set).Intersection(n.(*schema.Set))
   268  		d.Set("route", routes)
   269  
   270  		// Then loop through all the newly configured routes and create them
   271  		for _, route := range nrs.List() {
   272  			m := route.(map[string]interface{})
   273  
   274  			opts := ec2.CreateRouteInput{
   275  				RouteTableID:           aws.String(d.Id()),
   276  				DestinationCIDRBlock:   aws.String(m["cidr_block"].(string)),
   277  				GatewayID:              aws.String(m["gateway_id"].(string)),
   278  				InstanceID:             aws.String(m["instance_id"].(string)),
   279  				VPCPeeringConnectionID: aws.String(m["vpc_peering_connection_id"].(string)),
   280  				NetworkInterfaceID:     aws.String(m["network_interface_id"].(string)),
   281  			}
   282  
   283  			log.Printf("[INFO] Creating route for %s: %#v", d.Id(), opts)
   284  			if _, err := conn.CreateRoute(&opts); err != nil {
   285  				return err
   286  			}
   287  
   288  			routes.Add(route)
   289  			d.Set("route", routes)
   290  		}
   291  	}
   292  
   293  	if err := setTags(conn, d); err != nil {
   294  		return err
   295  	} else {
   296  		d.SetPartial("tags")
   297  	}
   298  
   299  	return resourceAwsRouteTableRead(d, meta)
   300  }
   301  
   302  func resourceAwsRouteTableDelete(d *schema.ResourceData, meta interface{}) error {
   303  	conn := meta.(*AWSClient).ec2conn
   304  
   305  	// First request the routing table since we'll have to disassociate
   306  	// all the subnets first.
   307  	rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(conn, d.Id())()
   308  	if err != nil {
   309  		return err
   310  	}
   311  	if rtRaw == nil {
   312  		return nil
   313  	}
   314  	rt := rtRaw.(*ec2.RouteTable)
   315  
   316  	// Do all the disassociations
   317  	for _, a := range rt.Associations {
   318  		log.Printf("[INFO] Disassociating association: %s", *a.RouteTableAssociationID)
   319  		_, err := conn.DisassociateRouteTable(&ec2.DisassociateRouteTableInput{
   320  			AssociationID: a.RouteTableAssociationID,
   321  		})
   322  		if err != nil {
   323  			return err
   324  		}
   325  	}
   326  
   327  	// Delete the route table
   328  	log.Printf("[INFO] Deleting Route Table: %s", d.Id())
   329  	_, err = conn.DeleteRouteTable(&ec2.DeleteRouteTableInput{
   330  		RouteTableID: aws.String(d.Id()),
   331  	})
   332  	if err != nil {
   333  		ec2err, ok := err.(awserr.Error)
   334  		if ok && ec2err.Code() == "InvalidRouteTableID.NotFound" {
   335  			return nil
   336  		}
   337  
   338  		return fmt.Errorf("Error deleting route table: %s", err)
   339  	}
   340  
   341  	// Wait for the route table to really destroy
   342  	log.Printf(
   343  		"[DEBUG] Waiting for route table (%s) to become destroyed",
   344  		d.Id())
   345  
   346  	stateConf := &resource.StateChangeConf{
   347  		Pending: []string{"ready"},
   348  		Target:  "",
   349  		Refresh: resourceAwsRouteTableStateRefreshFunc(conn, d.Id()),
   350  		Timeout: 1 * time.Minute,
   351  	}
   352  	if _, err := stateConf.WaitForState(); err != nil {
   353  		return fmt.Errorf(
   354  			"Error waiting for route table (%s) to become destroyed: %s",
   355  			d.Id(), err)
   356  	}
   357  
   358  	return nil
   359  }
   360  
   361  func resourceAwsRouteTableHash(v interface{}) int {
   362  	var buf bytes.Buffer
   363  	m := v.(map[string]interface{})
   364  
   365  	if v, ok := m["cidr_block"]; ok {
   366  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   367  	}
   368  
   369  	if v, ok := m["gateway_id"]; ok {
   370  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   371  	}
   372  
   373  	instanceSet := false
   374  	if v, ok := m["instance_id"]; ok {
   375  		instanceSet = v.(string) != ""
   376  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   377  	}
   378  
   379  	if v, ok := m["vpc_peering_connection_id"]; ok {
   380  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   381  	}
   382  
   383  	if v, ok := m["network_interface_id"]; ok && !instanceSet {
   384  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   385  	}
   386  
   387  	return hashcode.String(buf.String())
   388  }
   389  
   390  // resourceAwsRouteTableStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   391  // a RouteTable.
   392  func resourceAwsRouteTableStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
   393  	return func() (interface{}, string, error) {
   394  		resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesInput{
   395  			RouteTableIDs: []*string{aws.String(id)},
   396  		})
   397  		if err != nil {
   398  			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidRouteTableID.NotFound" {
   399  				resp = nil
   400  			} else {
   401  				log.Printf("Error on RouteTableStateRefresh: %s", err)
   402  				return nil, "", err
   403  			}
   404  		}
   405  
   406  		if resp == nil {
   407  			// Sometimes AWS just has consistency issues and doesn't see
   408  			// our instance yet. Return an empty state.
   409  			return nil, "", nil
   410  		}
   411  
   412  		rt := resp.RouteTables[0]
   413  		return rt, "ready", nil
   414  	}
   415  }