github.com/ezbercih/terraform@v0.1.1-0.20140729011846-3c33865e0839/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.ResourceState,
    18  	d *terraform.ResourceDiff,
    19  	meta interface{}) (*terraform.ResourceState, 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.ResourceState,
    60  	d *terraform.ResourceDiff,
    61  	meta interface{}) (*terraform.ResourceState, 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.ResourceState,
   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.ResourceState,
   192  	meta interface{}) (*terraform.ResourceState, 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.ResourceState,
   210  	c *terraform.ResourceConfig,
   211  	meta interface{}) (*terraform.ResourceDiff, 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.ResourceState,
   224  	rt *ec2.RouteTable) (*terraform.ResourceState, error) {
   225  	s.Attributes["vpc_id"] = rt.VpcId
   226  
   227  	// We belong to a VPC
   228  	s.Dependencies = []terraform.ResourceDependency{
   229  		terraform.ResourceDependency{ID: rt.VpcId},
   230  	}
   231  
   232  	return s, nil
   233  }
   234  
   235  // routeTableOp represents a minor operation on the routing table.
   236  // This tells us what we should do to the routing table.
   237  type routeTableOp struct {
   238  	Op    routeTableOpType
   239  	Route ec2.Route
   240  }
   241  
   242  // routeTableOpType is the type of operation related to a route that
   243  // can be operated on a routing table.
   244  type routeTableOpType byte
   245  
   246  const (
   247  	routeTableOpCreate routeTableOpType = iota
   248  	routeTableOpReplace
   249  	routeTableOpDelete
   250  )
   251  
   252  // routeTableOps takes the old and new routes from flatmap.Expand
   253  // and returns a set of operations that must be performed in order
   254  // to get to the desired state.
   255  func routeTableOps(a interface{}, b interface{}) []routeTableOp {
   256  	// Build up the actual ec2.Route objects
   257  	oldRoutes := make(map[string]ec2.Route)
   258  	newRoutes := make(map[string]ec2.Route)
   259  	for i, raws := range []interface{}{a, b} {
   260  		result := oldRoutes
   261  		if i == 1 {
   262  			result = newRoutes
   263  		}
   264  		if raws == nil {
   265  			continue
   266  		}
   267  
   268  		for _, raw := range raws.([]interface{}) {
   269  			m := raw.(map[string]interface{})
   270  			r := ec2.Route{
   271  				DestinationCidrBlock: m["cidr_block"].(string),
   272  			}
   273  			if v, ok := m["gateway_id"]; ok {
   274  				r.GatewayId = v.(string)
   275  			}
   276  			if v, ok := m["instance_id"]; ok {
   277  				r.InstanceId = v.(string)
   278  			}
   279  
   280  			result[r.DestinationCidrBlock] = r
   281  		}
   282  	}
   283  
   284  	// Now, start building up the ops
   285  	ops := make([]routeTableOp, 0, len(newRoutes))
   286  	for n, r := range newRoutes {
   287  		op := routeTableOpCreate
   288  		if oldR, ok := oldRoutes[n]; ok {
   289  			if reflect.DeepEqual(r, oldR) {
   290  				// No changes!
   291  				continue
   292  			}
   293  
   294  			op = routeTableOpReplace
   295  		}
   296  
   297  		ops = append(ops, routeTableOp{
   298  			Op:    op,
   299  			Route: r,
   300  		})
   301  	}
   302  
   303  	// Determine what routes we need to delete
   304  	for _, op := range ops {
   305  		delete(oldRoutes, op.Route.DestinationCidrBlock)
   306  	}
   307  	for _, r := range oldRoutes {
   308  		ops = append(ops, routeTableOp{
   309  			Op:    routeTableOpDelete,
   310  			Route: r,
   311  		})
   312  	}
   313  
   314  	return ops
   315  }
   316  
   317  // RouteTableStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   318  // a RouteTable.
   319  func RouteTableStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
   320  	return func() (interface{}, string, error) {
   321  		resp, err := conn.DescribeRouteTables([]string{id}, ec2.NewFilter())
   322  		if err != nil {
   323  			if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidRouteTableID.NotFound" {
   324  				resp = nil
   325  			} else {
   326  				log.Printf("Error on RouteTableStateRefresh: %s", err)
   327  				return nil, "", err
   328  			}
   329  		}
   330  
   331  		if resp == nil {
   332  			// Sometimes AWS just has consistency issues and doesn't see
   333  			// our instance yet. Return an empty state.
   334  			return nil, "", nil
   335  		}
   336  
   337  		rt := &resp.RouteTables[0]
   338  		return rt, "ready", nil
   339  	}
   340  }