github.com/jsoriano/terraform@v0.6.7-0.20151026070445-8b70867fdd95/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  		if r.DestinationPrefixListId != nil {
   152  			// Skipping because VPC endpoint routes are handled separately
   153  			// See aws_vpc_endpoint
   154  			continue
   155  		}
   156  
   157  		m := make(map[string]interface{})
   158  
   159  		if r.DestinationCidrBlock != nil {
   160  			m["cidr_block"] = *r.DestinationCidrBlock
   161  		}
   162  		if r.GatewayId != nil {
   163  			m["gateway_id"] = *r.GatewayId
   164  		}
   165  		if r.InstanceId != nil {
   166  			m["instance_id"] = *r.InstanceId
   167  		}
   168  		if r.VpcPeeringConnectionId != nil {
   169  			m["vpc_peering_connection_id"] = *r.VpcPeeringConnectionId
   170  		}
   171  		if r.NetworkInterfaceId != nil {
   172  			m["network_interface_id"] = *r.NetworkInterfaceId
   173  		}
   174  
   175  		route.Add(m)
   176  	}
   177  	d.Set("route", route)
   178  
   179  	// Tags
   180  	d.Set("tags", tagsToMap(rt.Tags))
   181  
   182  	return nil
   183  }
   184  
   185  func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error {
   186  	conn := meta.(*AWSClient).ec2conn
   187  
   188  	if d.HasChange("propagating_vgws") {
   189  		o, n := d.GetChange("propagating_vgws")
   190  		os := o.(*schema.Set)
   191  		ns := n.(*schema.Set)
   192  		remove := os.Difference(ns).List()
   193  		add := ns.Difference(os).List()
   194  
   195  		// Now first loop through all the old propagations and disable any obsolete ones
   196  		for _, vgw := range remove {
   197  			id := vgw.(string)
   198  
   199  			// Disable the propagation as it no longer exists in the config
   200  			log.Printf(
   201  				"[INFO] Deleting VGW propagation from %s: %s",
   202  				d.Id(), id)
   203  			_, err := conn.DisableVgwRoutePropagation(&ec2.DisableVgwRoutePropagationInput{
   204  				RouteTableId: aws.String(d.Id()),
   205  				GatewayId:    aws.String(id),
   206  			})
   207  			if err != nil {
   208  				return err
   209  			}
   210  		}
   211  
   212  		// Make sure we save the state of the currently configured rules
   213  		propagatingVGWs := os.Intersection(ns)
   214  		d.Set("propagating_vgws", propagatingVGWs)
   215  
   216  		// Then loop through all the newly configured propagations and enable them
   217  		for _, vgw := range add {
   218  			id := vgw.(string)
   219  
   220  			var err error
   221  			for i := 0; i < 5; i++ {
   222  				log.Printf("[INFO] Enabling VGW propagation for %s: %s", d.Id(), id)
   223  				_, err = conn.EnableVgwRoutePropagation(&ec2.EnableVgwRoutePropagationInput{
   224  					RouteTableId: aws.String(d.Id()),
   225  					GatewayId:    aws.String(id),
   226  				})
   227  				if err == nil {
   228  					break
   229  				}
   230  
   231  				// If we get a Gateway.NotAttached, it is usually some
   232  				// eventually consistency stuff. So we have to just wait a
   233  				// bit...
   234  				ec2err, ok := err.(awserr.Error)
   235  				if ok && ec2err.Code() == "Gateway.NotAttached" {
   236  					time.Sleep(20 * time.Second)
   237  					continue
   238  				}
   239  			}
   240  			if err != nil {
   241  				return err
   242  			}
   243  
   244  			propagatingVGWs.Add(vgw)
   245  			d.Set("propagating_vgws", propagatingVGWs)
   246  		}
   247  	}
   248  
   249  	// Check if the route set as a whole has changed
   250  	if d.HasChange("route") {
   251  		o, n := d.GetChange("route")
   252  		ors := o.(*schema.Set).Difference(n.(*schema.Set))
   253  		nrs := n.(*schema.Set).Difference(o.(*schema.Set))
   254  
   255  		// Now first loop through all the old routes and delete any obsolete ones
   256  		for _, route := range ors.List() {
   257  			m := route.(map[string]interface{})
   258  
   259  			// Delete the route as it no longer exists in the config
   260  			log.Printf(
   261  				"[INFO] Deleting route from %s: %s",
   262  				d.Id(), m["cidr_block"].(string))
   263  			_, err := conn.DeleteRoute(&ec2.DeleteRouteInput{
   264  				RouteTableId:         aws.String(d.Id()),
   265  				DestinationCidrBlock: aws.String(m["cidr_block"].(string)),
   266  			})
   267  			if err != nil {
   268  				return err
   269  			}
   270  		}
   271  
   272  		// Make sure we save the state of the currently configured rules
   273  		routes := o.(*schema.Set).Intersection(n.(*schema.Set))
   274  		d.Set("route", routes)
   275  
   276  		// Then loop through all the newly configured routes and create them
   277  		for _, route := range nrs.List() {
   278  			m := route.(map[string]interface{})
   279  
   280  			opts := ec2.CreateRouteInput{
   281  				RouteTableId:           aws.String(d.Id()),
   282  				DestinationCidrBlock:   aws.String(m["cidr_block"].(string)),
   283  				GatewayId:              aws.String(m["gateway_id"].(string)),
   284  				InstanceId:             aws.String(m["instance_id"].(string)),
   285  				VpcPeeringConnectionId: aws.String(m["vpc_peering_connection_id"].(string)),
   286  				NetworkInterfaceId:     aws.String(m["network_interface_id"].(string)),
   287  			}
   288  
   289  			log.Printf("[INFO] Creating route for %s: %#v", d.Id(), opts)
   290  			if _, err := conn.CreateRoute(&opts); err != nil {
   291  				return err
   292  			}
   293  
   294  			routes.Add(route)
   295  			d.Set("route", routes)
   296  		}
   297  	}
   298  
   299  	if err := setTags(conn, d); err != nil {
   300  		return err
   301  	} else {
   302  		d.SetPartial("tags")
   303  	}
   304  
   305  	return resourceAwsRouteTableRead(d, meta)
   306  }
   307  
   308  func resourceAwsRouteTableDelete(d *schema.ResourceData, meta interface{}) error {
   309  	conn := meta.(*AWSClient).ec2conn
   310  
   311  	// First request the routing table since we'll have to disassociate
   312  	// all the subnets first.
   313  	rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(conn, d.Id())()
   314  	if err != nil {
   315  		return err
   316  	}
   317  	if rtRaw == nil {
   318  		return nil
   319  	}
   320  	rt := rtRaw.(*ec2.RouteTable)
   321  
   322  	// Do all the disassociations
   323  	for _, a := range rt.Associations {
   324  		log.Printf("[INFO] Disassociating association: %s", *a.RouteTableAssociationId)
   325  		_, err := conn.DisassociateRouteTable(&ec2.DisassociateRouteTableInput{
   326  			AssociationId: a.RouteTableAssociationId,
   327  		})
   328  		if err != nil {
   329  			return err
   330  		}
   331  	}
   332  
   333  	// Delete the route table
   334  	log.Printf("[INFO] Deleting Route Table: %s", d.Id())
   335  	_, err = conn.DeleteRouteTable(&ec2.DeleteRouteTableInput{
   336  		RouteTableId: aws.String(d.Id()),
   337  	})
   338  	if err != nil {
   339  		ec2err, ok := err.(awserr.Error)
   340  		if ok && ec2err.Code() == "InvalidRouteTableID.NotFound" {
   341  			return nil
   342  		}
   343  
   344  		return fmt.Errorf("Error deleting route table: %s", err)
   345  	}
   346  
   347  	// Wait for the route table to really destroy
   348  	log.Printf(
   349  		"[DEBUG] Waiting for route table (%s) to become destroyed",
   350  		d.Id())
   351  
   352  	stateConf := &resource.StateChangeConf{
   353  		Pending: []string{"ready"},
   354  		Target:  "",
   355  		Refresh: resourceAwsRouteTableStateRefreshFunc(conn, d.Id()),
   356  		Timeout: 1 * time.Minute,
   357  	}
   358  	if _, err := stateConf.WaitForState(); err != nil {
   359  		return fmt.Errorf(
   360  			"Error waiting for route table (%s) to become destroyed: %s",
   361  			d.Id(), err)
   362  	}
   363  
   364  	return nil
   365  }
   366  
   367  func resourceAwsRouteTableHash(v interface{}) int {
   368  	var buf bytes.Buffer
   369  	m := v.(map[string]interface{})
   370  
   371  	if v, ok := m["cidr_block"]; ok {
   372  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   373  	}
   374  
   375  	if v, ok := m["gateway_id"]; ok {
   376  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   377  	}
   378  
   379  	instanceSet := false
   380  	if v, ok := m["instance_id"]; ok {
   381  		instanceSet = v.(string) != ""
   382  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   383  	}
   384  
   385  	if v, ok := m["vpc_peering_connection_id"]; ok {
   386  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   387  	}
   388  
   389  	if v, ok := m["network_interface_id"]; ok && !instanceSet {
   390  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   391  	}
   392  
   393  	return hashcode.String(buf.String())
   394  }
   395  
   396  // resourceAwsRouteTableStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   397  // a RouteTable.
   398  func resourceAwsRouteTableStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
   399  	return func() (interface{}, string, error) {
   400  		resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesInput{
   401  			RouteTableIds: []*string{aws.String(id)},
   402  		})
   403  		if err != nil {
   404  			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidRouteTableID.NotFound" {
   405  				resp = nil
   406  			} else {
   407  				log.Printf("Error on RouteTableStateRefresh: %s", err)
   408  				return nil, "", err
   409  			}
   410  		}
   411  
   412  		if resp == nil {
   413  			// Sometimes AWS just has consistency issues and doesn't see
   414  			// our instance yet. Return an empty state.
   415  			return nil, "", nil
   416  		}
   417  
   418  		rt := resp.RouteTables[0]
   419  		return rt, "ready", nil
   420  	}
   421  }