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 }