github.com/bengesoff/terraform@v0.3.1-0.20141018223233-b25a53629922/builtin/providers/aws/resource_aws_route_table.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"reflect"
     7  	"time"
     8  
     9  	"github.com/hashicorp/terraform/flatmap"
    10  	"github.com/hashicorp/terraform/helper/diff"
    11  	"github.com/hashicorp/terraform/helper/resource"
    12  	"github.com/hashicorp/terraform/terraform"
    13  	"github.com/mitchellh/goamz/ec2"
    14  )
    15  
    16  func resource_aws_route_table_create(
    17  	s *terraform.InstanceState,
    18  	d *terraform.InstanceDiff,
    19  	meta interface{}) (*terraform.InstanceState, error) {
    20  	p := meta.(*ResourceProvider)
    21  	ec2conn := p.ec2conn
    22  
    23  	// Create the routing table
    24  	createOpts := &ec2.CreateRouteTable{
    25  		VpcId: d.Attributes["vpc_id"].New,
    26  	}
    27  	log.Printf("[DEBUG] RouteTable create config: %#v", createOpts)
    28  	resp, err := ec2conn.CreateRouteTable(createOpts)
    29  	if err != nil {
    30  		return nil, fmt.Errorf("Error creating route table: %s", err)
    31  	}
    32  
    33  	// Get the ID and store it
    34  	rt := &resp.RouteTable
    35  	s.ID = rt.RouteTableId
    36  	log.Printf("[INFO] Route Table ID: %s", s.ID)
    37  
    38  	// Wait for the route table to become available
    39  	log.Printf(
    40  		"[DEBUG] Waiting for route table (%s) to become available",
    41  		s.ID)
    42  	stateConf := &resource.StateChangeConf{
    43  		Pending: []string{"pending"},
    44  		Target:  "ready",
    45  		Refresh: RouteTableStateRefreshFunc(ec2conn, s.ID),
    46  		Timeout: 1 * time.Minute,
    47  	}
    48  	if _, err := stateConf.WaitForState(); err != nil {
    49  		return s, fmt.Errorf(
    50  			"Error waiting for route table (%s) to become available: %s",
    51  			s.ID, err)
    52  	}
    53  
    54  	// Update our routes
    55  	return resource_aws_route_table_update(s, d, meta)
    56  }
    57  
    58  func resource_aws_route_table_update(
    59  	s *terraform.InstanceState,
    60  	d *terraform.InstanceDiff,
    61  	meta interface{}) (*terraform.InstanceState, error) {
    62  	p := meta.(*ResourceProvider)
    63  	ec2conn := p.ec2conn
    64  
    65  	// Our resulting state
    66  	rs := s.MergeDiff(d)
    67  
    68  	// Get our routes out of the merge
    69  	oldroutes := flatmap.Expand(s.Attributes, "route")
    70  	routes := flatmap.Expand(s.MergeDiff(d).Attributes, "route")
    71  
    72  	// Determine the route operations we need to perform
    73  	ops := routeTableOps(oldroutes, routes)
    74  	if len(ops) == 0 {
    75  		return s, nil
    76  	}
    77  
    78  	// Go through each operation, performing each one at a time.
    79  	// We store the updated state on each operation so that if any
    80  	// individual operation fails, we can return a valid partial state.
    81  	var err error
    82  	resultRoutes := make([]map[string]string, 0, len(ops))
    83  	for _, op := range ops {
    84  		switch op.Op {
    85  		case routeTableOpCreate:
    86  			opts := ec2.CreateRoute{
    87  				RouteTableId:         s.ID,
    88  				DestinationCidrBlock: op.Route.DestinationCidrBlock,
    89  				GatewayId:            op.Route.GatewayId,
    90  				InstanceId:           op.Route.InstanceId,
    91  			}
    92  
    93  			_, err = ec2conn.CreateRoute(&opts)
    94  		case routeTableOpReplace:
    95  			opts := ec2.ReplaceRoute{
    96  				RouteTableId:         s.ID,
    97  				DestinationCidrBlock: op.Route.DestinationCidrBlock,
    98  				GatewayId:            op.Route.GatewayId,
    99  				InstanceId:           op.Route.InstanceId,
   100  			}
   101  
   102  			_, err = ec2conn.ReplaceRoute(&opts)
   103  		case routeTableOpDelete:
   104  			_, err = ec2conn.DeleteRoute(
   105  				s.ID, op.Route.DestinationCidrBlock)
   106  		}
   107  
   108  		if err != nil {
   109  			// Exit early so we can return what we've done so far
   110  			break
   111  		}
   112  
   113  		// If we didn't delete the route, append it to the list of routes
   114  		// we have.
   115  		if op.Op != routeTableOpDelete {
   116  			resultMap := map[string]string{"cidr_block": op.Route.DestinationCidrBlock}
   117  			if op.Route.GatewayId != "" {
   118  				resultMap["gateway_id"] = op.Route.GatewayId
   119  			} else if op.Route.InstanceId != "" {
   120  				resultMap["instance_id"] = op.Route.InstanceId
   121  			}
   122  
   123  			resultRoutes = append(resultRoutes, resultMap)
   124  		}
   125  	}
   126  
   127  	// Update our state with the settings
   128  	flatmap.Map(rs.Attributes).Merge(flatmap.Flatten(map[string]interface{}{
   129  		"route": resultRoutes,
   130  	}))
   131  
   132  	return rs, err
   133  }
   134  
   135  func resource_aws_route_table_destroy(
   136  	s *terraform.InstanceState,
   137  	meta interface{}) error {
   138  	p := meta.(*ResourceProvider)
   139  	ec2conn := p.ec2conn
   140  
   141  	// First request the routing table since we'll have to disassociate
   142  	// all the subnets first.
   143  	rtRaw, _, err := RouteTableStateRefreshFunc(ec2conn, s.ID)()
   144  	if err != nil {
   145  		return err
   146  	}
   147  	if rtRaw == nil {
   148  		return nil
   149  	}
   150  	rt := rtRaw.(*ec2.RouteTable)
   151  
   152  	// Do all the disassociations
   153  	for _, a := range rt.Associations {
   154  		log.Printf("[INFO] Disassociating association: %s", a.AssociationId)
   155  		if _, err := ec2conn.DisassociateRouteTable(a.AssociationId); err != nil {
   156  			return err
   157  		}
   158  	}
   159  
   160  	// Delete the route table
   161  	log.Printf("[INFO] Deleting Route Table: %s", s.ID)
   162  	if _, err := ec2conn.DeleteRouteTable(s.ID); err != nil {
   163  		ec2err, ok := err.(*ec2.Error)
   164  		if ok && ec2err.Code == "InvalidRouteTableID.NotFound" {
   165  			return nil
   166  		}
   167  
   168  		return fmt.Errorf("Error deleting route table: %s", err)
   169  	}
   170  
   171  	// Wait for the route table to really destroy
   172  	log.Printf(
   173  		"[DEBUG] Waiting for route table (%s) to become destroyed",
   174  		s.ID)
   175  	stateConf := &resource.StateChangeConf{
   176  		Pending: []string{"ready"},
   177  		Target:  "",
   178  		Refresh: RouteTableStateRefreshFunc(ec2conn, s.ID),
   179  		Timeout: 1 * time.Minute,
   180  	}
   181  	if _, err := stateConf.WaitForState(); err != nil {
   182  		return fmt.Errorf(
   183  			"Error waiting for route table (%s) to become destroyed: %s",
   184  			s.ID, err)
   185  	}
   186  
   187  	return nil
   188  }
   189  
   190  func resource_aws_route_table_refresh(
   191  	s *terraform.InstanceState,
   192  	meta interface{}) (*terraform.InstanceState, error) {
   193  	p := meta.(*ResourceProvider)
   194  	ec2conn := p.ec2conn
   195  
   196  	rtRaw, _, err := RouteTableStateRefreshFunc(ec2conn, s.ID)()
   197  	if err != nil {
   198  		return s, err
   199  	}
   200  	if rtRaw == nil {
   201  		return nil, nil
   202  	}
   203  
   204  	rt := rtRaw.(*ec2.RouteTable)
   205  	return resource_aws_route_table_update_state(s, rt)
   206  }
   207  
   208  func resource_aws_route_table_diff(
   209  	s *terraform.InstanceState,
   210  	c *terraform.ResourceConfig,
   211  	meta interface{}) (*terraform.InstanceDiff, error) {
   212  	b := &diff.ResourceBuilder{
   213  		Attrs: map[string]diff.AttrType{
   214  			"vpc_id": diff.AttrTypeCreate,
   215  			"route":  diff.AttrTypeUpdate,
   216  		},
   217  	}
   218  
   219  	return b.Diff(s, c)
   220  }
   221  
   222  func resource_aws_route_table_update_state(
   223  	s *terraform.InstanceState,
   224  	rt *ec2.RouteTable) (*terraform.InstanceState, error) {
   225  	s.Attributes["vpc_id"] = rt.VpcId
   226  
   227  	return s, nil
   228  }
   229  
   230  // routeTableOp represents a minor operation on the routing table.
   231  // This tells us what we should do to the routing table.
   232  type routeTableOp struct {
   233  	Op    routeTableOpType
   234  	Route ec2.Route
   235  }
   236  
   237  // routeTableOpType is the type of operation related to a route that
   238  // can be operated on a routing table.
   239  type routeTableOpType byte
   240  
   241  const (
   242  	routeTableOpCreate routeTableOpType = iota
   243  	routeTableOpReplace
   244  	routeTableOpDelete
   245  )
   246  
   247  // routeTableOps takes the old and new routes from flatmap.Expand
   248  // and returns a set of operations that must be performed in order
   249  // to get to the desired state.
   250  func routeTableOps(a interface{}, b interface{}) []routeTableOp {
   251  	// Build up the actual ec2.Route objects
   252  	oldRoutes := make(map[string]ec2.Route)
   253  	newRoutes := make(map[string]ec2.Route)
   254  	for i, raws := range []interface{}{a, b} {
   255  		result := oldRoutes
   256  		if i == 1 {
   257  			result = newRoutes
   258  		}
   259  		if raws == nil {
   260  			continue
   261  		}
   262  
   263  		for _, raw := range raws.([]interface{}) {
   264  			m := raw.(map[string]interface{})
   265  			r := ec2.Route{
   266  				DestinationCidrBlock: m["cidr_block"].(string),
   267  			}
   268  			if v, ok := m["gateway_id"]; ok {
   269  				r.GatewayId = v.(string)
   270  			}
   271  			if v, ok := m["instance_id"]; ok {
   272  				r.InstanceId = v.(string)
   273  			}
   274  
   275  			result[r.DestinationCidrBlock] = r
   276  		}
   277  	}
   278  
   279  	// Now, start building up the ops
   280  	ops := make([]routeTableOp, 0, len(newRoutes))
   281  	for n, r := range newRoutes {
   282  		op := routeTableOpCreate
   283  		if oldR, ok := oldRoutes[n]; ok {
   284  			if reflect.DeepEqual(r, oldR) {
   285  				// No changes!
   286  				continue
   287  			}
   288  
   289  			op = routeTableOpReplace
   290  		}
   291  
   292  		ops = append(ops, routeTableOp{
   293  			Op:    op,
   294  			Route: r,
   295  		})
   296  	}
   297  
   298  	// Determine what routes we need to delete
   299  	for _, op := range ops {
   300  		delete(oldRoutes, op.Route.DestinationCidrBlock)
   301  	}
   302  	for _, r := range oldRoutes {
   303  		ops = append(ops, routeTableOp{
   304  			Op:    routeTableOpDelete,
   305  			Route: r,
   306  		})
   307  	}
   308  
   309  	return ops
   310  }
   311  
   312  // RouteTableStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   313  // a RouteTable.
   314  func RouteTableStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
   315  	return func() (interface{}, string, error) {
   316  		resp, err := conn.DescribeRouteTables([]string{id}, ec2.NewFilter())
   317  		if err != nil {
   318  			if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidRouteTableID.NotFound" {
   319  				resp = nil
   320  			} else {
   321  				log.Printf("Error on RouteTableStateRefresh: %s", err)
   322  				return nil, "", err
   323  			}
   324  		}
   325  
   326  		if resp == nil {
   327  			// Sometimes AWS just has consistency issues and doesn't see
   328  			// our instance yet. Return an empty state.
   329  			return nil, "", nil
   330  		}
   331  
   332  		rt := &resp.RouteTables[0]
   333  		return rt, "ready", nil
   334  	}
   335  }