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 }