github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/aws/resource_aws_vpc.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "time" 7 8 "github.com/aws/aws-sdk-go/aws" 9 "github.com/aws/aws-sdk-go/aws/awserr" 10 "github.com/aws/aws-sdk-go/service/ec2" 11 "github.com/hashicorp/terraform/helper/resource" 12 "github.com/hashicorp/terraform/helper/schema" 13 ) 14 15 func resourceAwsVpc() *schema.Resource { 16 return &schema.Resource{ 17 Create: resourceAwsVpcCreate, 18 Read: resourceAwsVpcRead, 19 Update: resourceAwsVpcUpdate, 20 Delete: resourceAwsVpcDelete, 21 Importer: &schema.ResourceImporter{ 22 State: resourceAwsVpcInstanceImport, 23 }, 24 25 SchemaVersion: 1, 26 MigrateState: resourceAwsVpcMigrateState, 27 28 Schema: map[string]*schema.Schema{ 29 "cidr_block": { 30 Type: schema.TypeString, 31 Required: true, 32 ForceNew: true, 33 ValidateFunc: validateCIDRNetworkAddress, 34 }, 35 36 "instance_tenancy": { 37 Type: schema.TypeString, 38 Optional: true, 39 ForceNew: true, 40 Computed: true, 41 }, 42 43 "enable_dns_hostnames": { 44 Type: schema.TypeBool, 45 Optional: true, 46 Computed: true, 47 }, 48 49 "enable_dns_support": { 50 Type: schema.TypeBool, 51 Optional: true, 52 Default: true, 53 }, 54 55 "enable_classiclink": { 56 Type: schema.TypeBool, 57 Optional: true, 58 Computed: true, 59 }, 60 61 "assign_generated_ipv6_cidr_block": { 62 Type: schema.TypeBool, 63 Optional: true, 64 Default: false, 65 }, 66 67 "main_route_table_id": { 68 Type: schema.TypeString, 69 Computed: true, 70 }, 71 72 "default_network_acl_id": { 73 Type: schema.TypeString, 74 Computed: true, 75 }, 76 77 "dhcp_options_id": { 78 Type: schema.TypeString, 79 Computed: true, 80 }, 81 82 "default_security_group_id": { 83 Type: schema.TypeString, 84 Computed: true, 85 }, 86 87 "default_route_table_id": { 88 Type: schema.TypeString, 89 Computed: true, 90 }, 91 92 "ipv6_association_id": { 93 Type: schema.TypeString, 94 Computed: true, 95 }, 96 97 "ipv6_cidr_block": { 98 Type: schema.TypeString, 99 Computed: true, 100 }, 101 102 "tags": tagsSchema(), 103 }, 104 } 105 } 106 107 func resourceAwsVpcCreate(d *schema.ResourceData, meta interface{}) error { 108 conn := meta.(*AWSClient).ec2conn 109 instance_tenancy := "default" 110 if v, ok := d.GetOk("instance_tenancy"); ok { 111 instance_tenancy = v.(string) 112 } 113 114 // Create the VPC 115 createOpts := &ec2.CreateVpcInput{ 116 CidrBlock: aws.String(d.Get("cidr_block").(string)), 117 InstanceTenancy: aws.String(instance_tenancy), 118 AmazonProvidedIpv6CidrBlock: aws.Bool(d.Get("assign_generated_ipv6_cidr_block").(bool)), 119 } 120 121 log.Printf("[DEBUG] VPC create config: %#v", *createOpts) 122 vpcResp, err := conn.CreateVpc(createOpts) 123 if err != nil { 124 return fmt.Errorf("Error creating VPC: %s", err) 125 } 126 127 // Get the ID and store it 128 vpc := vpcResp.Vpc 129 d.SetId(*vpc.VpcId) 130 log.Printf("[INFO] VPC ID: %s", d.Id()) 131 132 // Set partial mode and say that we setup the cidr block 133 d.Partial(true) 134 d.SetPartial("cidr_block") 135 136 // Wait for the VPC to become available 137 log.Printf( 138 "[DEBUG] Waiting for VPC (%s) to become available", 139 d.Id()) 140 stateConf := &resource.StateChangeConf{ 141 Pending: []string{"pending"}, 142 Target: []string{"available"}, 143 Refresh: VPCStateRefreshFunc(conn, d.Id()), 144 Timeout: 10 * time.Minute, 145 } 146 if _, err := stateConf.WaitForState(); err != nil { 147 return fmt.Errorf( 148 "Error waiting for VPC (%s) to become available: %s", 149 d.Id(), err) 150 } 151 152 // Update our attributes and return 153 return resourceAwsVpcUpdate(d, meta) 154 } 155 156 func resourceAwsVpcRead(d *schema.ResourceData, meta interface{}) error { 157 conn := meta.(*AWSClient).ec2conn 158 159 // Refresh the VPC state 160 vpcRaw, _, err := VPCStateRefreshFunc(conn, d.Id())() 161 if err != nil { 162 return err 163 } 164 if vpcRaw == nil { 165 d.SetId("") 166 return nil 167 } 168 169 // VPC stuff 170 vpc := vpcRaw.(*ec2.Vpc) 171 vpcid := d.Id() 172 d.Set("cidr_block", vpc.CidrBlock) 173 d.Set("dhcp_options_id", vpc.DhcpOptionsId) 174 d.Set("instance_tenancy", vpc.InstanceTenancy) 175 176 // Tags 177 d.Set("tags", tagsToMap(vpc.Tags)) 178 179 for _, a := range vpc.Ipv6CidrBlockAssociationSet { 180 if *a.Ipv6CidrBlockState.State == "associated" { //we can only ever have 1 IPv6 block associated at once 181 d.Set("assign_generated_ipv6_cidr_block", true) 182 d.Set("ipv6_association_id", a.AssociationId) 183 d.Set("ipv6_cidr_block", a.Ipv6CidrBlock) 184 } else { 185 d.Set("assign_generated_ipv6_cidr_block", false) 186 d.Set("ipv6_association_id", "") // we blank these out to remove old entries 187 d.Set("ipv6_cidr_block", "") 188 } 189 } 190 191 // Attributes 192 attribute := "enableDnsSupport" 193 DescribeAttrOpts := &ec2.DescribeVpcAttributeInput{ 194 Attribute: aws.String(attribute), 195 VpcId: aws.String(vpcid), 196 } 197 resp, err := conn.DescribeVpcAttribute(DescribeAttrOpts) 198 if err != nil { 199 return err 200 } 201 d.Set("enable_dns_support", *resp.EnableDnsSupport.Value) 202 attribute = "enableDnsHostnames" 203 DescribeAttrOpts = &ec2.DescribeVpcAttributeInput{ 204 Attribute: &attribute, 205 VpcId: &vpcid, 206 } 207 resp, err = conn.DescribeVpcAttribute(DescribeAttrOpts) 208 if err != nil { 209 return err 210 } 211 d.Set("enable_dns_hostnames", *resp.EnableDnsHostnames.Value) 212 213 DescribeClassiclinkOpts := &ec2.DescribeVpcClassicLinkInput{ 214 VpcIds: []*string{&vpcid}, 215 } 216 217 // Classic Link is only available in regions that support EC2 Classic 218 respClassiclink, err := conn.DescribeVpcClassicLink(DescribeClassiclinkOpts) 219 if err != nil { 220 if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "UnsupportedOperation" { 221 log.Printf("[WARN] VPC Classic Link is not supported in this region") 222 } else { 223 return err 224 } 225 } else { 226 classiclink_enabled := false 227 for _, v := range respClassiclink.Vpcs { 228 if *v.VpcId == vpcid { 229 if v.ClassicLinkEnabled != nil { 230 classiclink_enabled = *v.ClassicLinkEnabled 231 } 232 break 233 } 234 } 235 d.Set("enable_classiclink", classiclink_enabled) 236 } 237 238 // Get the main routing table for this VPC 239 // Really Ugly need to make this better - rmenn 240 filter1 := &ec2.Filter{ 241 Name: aws.String("association.main"), 242 Values: []*string{aws.String("true")}, 243 } 244 filter2 := &ec2.Filter{ 245 Name: aws.String("vpc-id"), 246 Values: []*string{aws.String(d.Id())}, 247 } 248 DescribeRouteOpts := &ec2.DescribeRouteTablesInput{ 249 Filters: []*ec2.Filter{filter1, filter2}, 250 } 251 routeResp, err := conn.DescribeRouteTables(DescribeRouteOpts) 252 if err != nil { 253 return err 254 } 255 if v := routeResp.RouteTables; len(v) > 0 { 256 d.Set("main_route_table_id", *v[0].RouteTableId) 257 } 258 259 if err := resourceAwsVpcSetDefaultNetworkAcl(conn, d); err != nil { 260 log.Printf("[WARN] Unable to set Default Network ACL: %s", err) 261 } 262 if err := resourceAwsVpcSetDefaultSecurityGroup(conn, d); err != nil { 263 log.Printf("[WARN] Unable to set Default Security Group: %s", err) 264 } 265 if err := resourceAwsVpcSetDefaultRouteTable(conn, d); err != nil { 266 log.Printf("[WARN] Unable to set Default Route Table: %s", err) 267 } 268 269 return nil 270 } 271 272 func resourceAwsVpcUpdate(d *schema.ResourceData, meta interface{}) error { 273 conn := meta.(*AWSClient).ec2conn 274 275 // Turn on partial mode 276 d.Partial(true) 277 vpcid := d.Id() 278 if d.HasChange("enable_dns_hostnames") { 279 val := d.Get("enable_dns_hostnames").(bool) 280 modifyOpts := &ec2.ModifyVpcAttributeInput{ 281 VpcId: &vpcid, 282 EnableDnsHostnames: &ec2.AttributeBooleanValue{ 283 Value: &val, 284 }, 285 } 286 287 log.Printf( 288 "[INFO] Modifying enable_dns_hostnames vpc attribute for %s: %s", 289 d.Id(), modifyOpts) 290 if _, err := conn.ModifyVpcAttribute(modifyOpts); err != nil { 291 return err 292 } 293 294 d.SetPartial("enable_dns_hostnames") 295 } 296 297 _, hasEnableDnsSupportOption := d.GetOk("enable_dns_support") 298 299 if !hasEnableDnsSupportOption || d.HasChange("enable_dns_support") { 300 val := d.Get("enable_dns_support").(bool) 301 modifyOpts := &ec2.ModifyVpcAttributeInput{ 302 VpcId: &vpcid, 303 EnableDnsSupport: &ec2.AttributeBooleanValue{ 304 Value: &val, 305 }, 306 } 307 308 log.Printf( 309 "[INFO] Modifying enable_dns_support vpc attribute for %s: %s", 310 d.Id(), modifyOpts) 311 if _, err := conn.ModifyVpcAttribute(modifyOpts); err != nil { 312 return err 313 } 314 315 d.SetPartial("enable_dns_support") 316 } 317 318 if d.HasChange("enable_classiclink") { 319 val := d.Get("enable_classiclink").(bool) 320 321 if val { 322 modifyOpts := &ec2.EnableVpcClassicLinkInput{ 323 VpcId: &vpcid, 324 } 325 log.Printf( 326 "[INFO] Modifying enable_classiclink vpc attribute for %s: %#v", 327 d.Id(), modifyOpts) 328 if _, err := conn.EnableVpcClassicLink(modifyOpts); err != nil { 329 return err 330 } 331 } else { 332 modifyOpts := &ec2.DisableVpcClassicLinkInput{ 333 VpcId: &vpcid, 334 } 335 log.Printf( 336 "[INFO] Modifying enable_classiclink vpc attribute for %s: %#v", 337 d.Id(), modifyOpts) 338 if _, err := conn.DisableVpcClassicLink(modifyOpts); err != nil { 339 return err 340 } 341 } 342 343 d.SetPartial("enable_classiclink") 344 } 345 346 if d.HasChange("assign_generated_ipv6_cidr_block") && !d.IsNewResource() { 347 toAssign := d.Get("assign_generated_ipv6_cidr_block").(bool) 348 349 log.Printf("[INFO] Modifying assign_generated_ipv6_cidr_block to %#v", toAssign) 350 351 if toAssign { 352 modifyOpts := &ec2.AssociateVpcCidrBlockInput{ 353 VpcId: &vpcid, 354 AmazonProvidedIpv6CidrBlock: aws.Bool(toAssign), 355 } 356 log.Printf("[INFO] Enabling assign_generated_ipv6_cidr_block vpc attribute for %s: %#v", 357 d.Id(), modifyOpts) 358 resp, err := conn.AssociateVpcCidrBlock(modifyOpts) 359 if err != nil { 360 return err 361 } 362 363 // Wait for the CIDR to become available 364 log.Printf( 365 "[DEBUG] Waiting for IPv6 CIDR (%s) to become associated", 366 d.Id()) 367 stateConf := &resource.StateChangeConf{ 368 Pending: []string{"associating", "disassociated"}, 369 Target: []string{"associated"}, 370 Refresh: Ipv6CidrStateRefreshFunc(conn, d.Id(), *resp.Ipv6CidrBlockAssociation.AssociationId), 371 Timeout: 1 * time.Minute, 372 } 373 if _, err := stateConf.WaitForState(); err != nil { 374 return fmt.Errorf( 375 "Error waiting for IPv6 CIDR (%s) to become associated: %s", 376 d.Id(), err) 377 } 378 } else { 379 modifyOpts := &ec2.DisassociateVpcCidrBlockInput{ 380 AssociationId: aws.String(d.Get("ipv6_association_id").(string)), 381 } 382 log.Printf("[INFO] Disabling assign_generated_ipv6_cidr_block vpc attribute for %s: %#v", 383 d.Id(), modifyOpts) 384 if _, err := conn.DisassociateVpcCidrBlock(modifyOpts); err != nil { 385 return err 386 } 387 388 // Wait for the CIDR to become available 389 log.Printf( 390 "[DEBUG] Waiting for IPv6 CIDR (%s) to become disassociated", 391 d.Id()) 392 stateConf := &resource.StateChangeConf{ 393 Pending: []string{"disassociating", "associated"}, 394 Target: []string{"disassociated"}, 395 Refresh: Ipv6CidrStateRefreshFunc(conn, d.Id(), d.Get("ipv6_association_id").(string)), 396 Timeout: 1 * time.Minute, 397 } 398 if _, err := stateConf.WaitForState(); err != nil { 399 return fmt.Errorf( 400 "Error waiting for IPv6 CIDR (%s) to become disassociated: %s", 401 d.Id(), err) 402 } 403 } 404 405 d.SetPartial("assign_generated_ipv6_cidr_block") 406 } 407 408 if err := setTags(conn, d); err != nil { 409 return err 410 } else { 411 d.SetPartial("tags") 412 } 413 414 d.Partial(false) 415 return resourceAwsVpcRead(d, meta) 416 } 417 418 func resourceAwsVpcDelete(d *schema.ResourceData, meta interface{}) error { 419 conn := meta.(*AWSClient).ec2conn 420 vpcID := d.Id() 421 DeleteVpcOpts := &ec2.DeleteVpcInput{ 422 VpcId: &vpcID, 423 } 424 log.Printf("[INFO] Deleting VPC: %s", d.Id()) 425 426 return resource.Retry(5*time.Minute, func() *resource.RetryError { 427 _, err := conn.DeleteVpc(DeleteVpcOpts) 428 if err == nil { 429 return nil 430 } 431 432 ec2err, ok := err.(awserr.Error) 433 if !ok { 434 return resource.NonRetryableError(err) 435 } 436 437 switch ec2err.Code() { 438 case "InvalidVpcID.NotFound": 439 return nil 440 case "DependencyViolation": 441 return resource.RetryableError(err) 442 } 443 444 return resource.NonRetryableError(fmt.Errorf("Error deleting VPC: %s", err)) 445 }) 446 } 447 448 // VPCStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 449 // a VPC. 450 func VPCStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { 451 return func() (interface{}, string, error) { 452 DescribeVpcOpts := &ec2.DescribeVpcsInput{ 453 VpcIds: []*string{aws.String(id)}, 454 } 455 resp, err := conn.DescribeVpcs(DescribeVpcOpts) 456 if err != nil { 457 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpcID.NotFound" { 458 resp = nil 459 } else { 460 log.Printf("Error on VPCStateRefresh: %s", err) 461 return nil, "", err 462 } 463 } 464 465 if resp == nil { 466 // Sometimes AWS just has consistency issues and doesn't see 467 // our instance yet. Return an empty state. 468 return nil, "", nil 469 } 470 471 vpc := resp.Vpcs[0] 472 return vpc, *vpc.State, nil 473 } 474 } 475 476 func Ipv6CidrStateRefreshFunc(conn *ec2.EC2, id string, associationId string) resource.StateRefreshFunc { 477 return func() (interface{}, string, error) { 478 describeVpcOpts := &ec2.DescribeVpcsInput{ 479 VpcIds: []*string{aws.String(id)}, 480 } 481 resp, err := conn.DescribeVpcs(describeVpcOpts) 482 if err != nil { 483 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpcID.NotFound" { 484 resp = nil 485 } else { 486 log.Printf("Error on VPCStateRefresh: %s", err) 487 return nil, "", err 488 } 489 } 490 491 if resp == nil { 492 // Sometimes AWS just has consistency issues and doesn't see 493 // our instance yet. Return an empty state. 494 return nil, "", nil 495 } 496 497 if resp.Vpcs[0].Ipv6CidrBlockAssociationSet == nil { 498 return nil, "", nil 499 } 500 501 for _, association := range resp.Vpcs[0].Ipv6CidrBlockAssociationSet { 502 if *association.AssociationId == associationId { 503 return association, *association.Ipv6CidrBlockState.State, nil 504 } 505 } 506 507 return nil, "", nil 508 } 509 } 510 511 func resourceAwsVpcSetDefaultNetworkAcl(conn *ec2.EC2, d *schema.ResourceData) error { 512 filter1 := &ec2.Filter{ 513 Name: aws.String("default"), 514 Values: []*string{aws.String("true")}, 515 } 516 filter2 := &ec2.Filter{ 517 Name: aws.String("vpc-id"), 518 Values: []*string{aws.String(d.Id())}, 519 } 520 DescribeNetworkACLOpts := &ec2.DescribeNetworkAclsInput{ 521 Filters: []*ec2.Filter{filter1, filter2}, 522 } 523 networkAclResp, err := conn.DescribeNetworkAcls(DescribeNetworkACLOpts) 524 525 if err != nil { 526 return err 527 } 528 if v := networkAclResp.NetworkAcls; len(v) > 0 { 529 d.Set("default_network_acl_id", v[0].NetworkAclId) 530 } 531 532 return nil 533 } 534 535 func resourceAwsVpcSetDefaultSecurityGroup(conn *ec2.EC2, d *schema.ResourceData) error { 536 filter1 := &ec2.Filter{ 537 Name: aws.String("group-name"), 538 Values: []*string{aws.String("default")}, 539 } 540 filter2 := &ec2.Filter{ 541 Name: aws.String("vpc-id"), 542 Values: []*string{aws.String(d.Id())}, 543 } 544 DescribeSgOpts := &ec2.DescribeSecurityGroupsInput{ 545 Filters: []*ec2.Filter{filter1, filter2}, 546 } 547 securityGroupResp, err := conn.DescribeSecurityGroups(DescribeSgOpts) 548 549 if err != nil { 550 return err 551 } 552 if v := securityGroupResp.SecurityGroups; len(v) > 0 { 553 d.Set("default_security_group_id", v[0].GroupId) 554 } 555 556 return nil 557 } 558 559 func resourceAwsVpcSetDefaultRouteTable(conn *ec2.EC2, d *schema.ResourceData) error { 560 filter1 := &ec2.Filter{ 561 Name: aws.String("association.main"), 562 Values: []*string{aws.String("true")}, 563 } 564 filter2 := &ec2.Filter{ 565 Name: aws.String("vpc-id"), 566 Values: []*string{aws.String(d.Id())}, 567 } 568 569 findOpts := &ec2.DescribeRouteTablesInput{ 570 Filters: []*ec2.Filter{filter1, filter2}, 571 } 572 573 resp, err := conn.DescribeRouteTables(findOpts) 574 if err != nil { 575 return err 576 } 577 578 if len(resp.RouteTables) < 1 || resp.RouteTables[0] == nil { 579 return fmt.Errorf("Default Route table not found") 580 } 581 582 // There Can Be Only 1 ... Default Route Table 583 d.Set("default_route_table_id", resp.RouteTables[0].RouteTableId) 584 585 return nil 586 } 587 588 func resourceAwsVpcInstanceImport( 589 d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { 590 d.Set("assign_generated_ipv6_cidr_block", false) 591 return []*schema.ResourceData{d}, nil 592 }