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