github.com/dougneal/terraform@v0.6.15-0.20170330092735-b6a3840768a4/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 ForceNew: true, 64 Optional: true, 65 Default: false, 66 }, 67 68 "main_route_table_id": { 69 Type: schema.TypeString, 70 Computed: true, 71 }, 72 73 "default_network_acl_id": { 74 Type: schema.TypeString, 75 Computed: true, 76 }, 77 78 "dhcp_options_id": { 79 Type: schema.TypeString, 80 Computed: true, 81 }, 82 83 "default_security_group_id": { 84 Type: schema.TypeString, 85 Computed: true, 86 }, 87 88 "default_route_table_id": { 89 Type: schema.TypeString, 90 Computed: true, 91 }, 92 93 "ipv6_association_id": { 94 Type: schema.TypeString, 95 Computed: true, 96 }, 97 98 "ipv6_cidr_block": { 99 Type: schema.TypeString, 100 Computed: true, 101 }, 102 103 "tags": tagsSchema(), 104 }, 105 } 106 } 107 108 func resourceAwsVpcCreate(d *schema.ResourceData, meta interface{}) error { 109 conn := meta.(*AWSClient).ec2conn 110 instance_tenancy := "default" 111 if v, ok := d.GetOk("instance_tenancy"); ok { 112 instance_tenancy = v.(string) 113 } 114 115 // Create the VPC 116 createOpts := &ec2.CreateVpcInput{ 117 CidrBlock: aws.String(d.Get("cidr_block").(string)), 118 InstanceTenancy: aws.String(instance_tenancy), 119 AmazonProvidedIpv6CidrBlock: aws.Bool(d.Get("assign_generated_ipv6_cidr_block").(bool)), 120 } 121 122 log.Printf("[DEBUG] VPC create config: %#v", *createOpts) 123 vpcResp, err := conn.CreateVpc(createOpts) 124 if err != nil { 125 return fmt.Errorf("Error creating VPC: %s", err) 126 } 127 128 // Get the ID and store it 129 vpc := vpcResp.Vpc 130 d.SetId(*vpc.VpcId) 131 log.Printf("[INFO] VPC ID: %s", d.Id()) 132 133 // Set partial mode and say that we setup the cidr block 134 d.Partial(true) 135 d.SetPartial("cidr_block") 136 137 // Wait for the VPC to become available 138 log.Printf( 139 "[DEBUG] Waiting for VPC (%s) to become available", 140 d.Id()) 141 stateConf := &resource.StateChangeConf{ 142 Pending: []string{"pending"}, 143 Target: []string{"available"}, 144 Refresh: VPCStateRefreshFunc(conn, d.Id()), 145 Timeout: 10 * time.Minute, 146 } 147 if _, err := stateConf.WaitForState(); err != nil { 148 return fmt.Errorf( 149 "Error waiting for VPC (%s) to become available: %s", 150 d.Id(), err) 151 } 152 153 // Update our attributes and return 154 return resourceAwsVpcUpdate(d, meta) 155 } 156 157 func resourceAwsVpcRead(d *schema.ResourceData, meta interface{}) error { 158 conn := meta.(*AWSClient).ec2conn 159 160 // Refresh the VPC state 161 vpcRaw, _, err := VPCStateRefreshFunc(conn, d.Id())() 162 if err != nil { 163 return err 164 } 165 if vpcRaw == nil { 166 d.SetId("") 167 return nil 168 } 169 170 // VPC stuff 171 vpc := vpcRaw.(*ec2.Vpc) 172 vpcid := d.Id() 173 d.Set("cidr_block", vpc.CidrBlock) 174 d.Set("dhcp_options_id", vpc.DhcpOptionsId) 175 d.Set("instance_tenancy", vpc.InstanceTenancy) 176 177 // Tags 178 d.Set("tags", tagsToMap(vpc.Tags)) 179 180 for _, a := range vpc.Ipv6CidrBlockAssociationSet { 181 if *a.Ipv6CidrBlockState.State == "associated" { 182 d.Set("assign_generated_ipv6_cidr_block", true) 183 d.Set("ipv6_association_id", a.AssociationId) 184 d.Set("ipv6_cidr_block", a.Ipv6CidrBlock) 185 } else { 186 d.Set("assign_generated_ipv6_cidr_block", false) 187 d.Set("ipv6_association_id", "") // we blank these out to remove old entries 188 d.Set("ipv6_cidr_block", "") 189 } 190 } 191 192 // Attributes 193 attribute := "enableDnsSupport" 194 DescribeAttrOpts := &ec2.DescribeVpcAttributeInput{ 195 Attribute: aws.String(attribute), 196 VpcId: aws.String(vpcid), 197 } 198 resp, err := conn.DescribeVpcAttribute(DescribeAttrOpts) 199 if err != nil { 200 return err 201 } 202 d.Set("enable_dns_support", *resp.EnableDnsSupport.Value) 203 attribute = "enableDnsHostnames" 204 DescribeAttrOpts = &ec2.DescribeVpcAttributeInput{ 205 Attribute: &attribute, 206 VpcId: &vpcid, 207 } 208 resp, err = conn.DescribeVpcAttribute(DescribeAttrOpts) 209 if err != nil { 210 return err 211 } 212 d.Set("enable_dns_hostnames", *resp.EnableDnsHostnames.Value) 213 214 DescribeClassiclinkOpts := &ec2.DescribeVpcClassicLinkInput{ 215 VpcIds: []*string{&vpcid}, 216 } 217 218 // Classic Link is only available in regions that support EC2 Classic 219 respClassiclink, err := conn.DescribeVpcClassicLink(DescribeClassiclinkOpts) 220 if err != nil { 221 if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "UnsupportedOperation" { 222 log.Printf("[WARN] VPC Classic Link is not supported in this region") 223 } else { 224 return err 225 } 226 } else { 227 classiclink_enabled := false 228 for _, v := range respClassiclink.Vpcs { 229 if *v.VpcId == vpcid { 230 if v.ClassicLinkEnabled != nil { 231 classiclink_enabled = *v.ClassicLinkEnabled 232 } 233 break 234 } 235 } 236 d.Set("enable_classiclink", classiclink_enabled) 237 } 238 239 // Get the main routing table for this VPC 240 // Really Ugly need to make this better - rmenn 241 filter1 := &ec2.Filter{ 242 Name: aws.String("association.main"), 243 Values: []*string{aws.String("true")}, 244 } 245 filter2 := &ec2.Filter{ 246 Name: aws.String("vpc-id"), 247 Values: []*string{aws.String(d.Id())}, 248 } 249 DescribeRouteOpts := &ec2.DescribeRouteTablesInput{ 250 Filters: []*ec2.Filter{filter1, filter2}, 251 } 252 routeResp, err := conn.DescribeRouteTables(DescribeRouteOpts) 253 if err != nil { 254 return err 255 } 256 if v := routeResp.RouteTables; len(v) > 0 { 257 d.Set("main_route_table_id", *v[0].RouteTableId) 258 } 259 260 if err := resourceAwsVpcSetDefaultNetworkAcl(conn, d); err != nil { 261 log.Printf("[WARN] Unable to set Default Network ACL: %s", err) 262 } 263 if err := resourceAwsVpcSetDefaultSecurityGroup(conn, d); err != nil { 264 log.Printf("[WARN] Unable to set Default Security Group: %s", err) 265 } 266 if err := resourceAwsVpcSetDefaultRouteTable(conn, d); err != nil { 267 log.Printf("[WARN] Unable to set Default Route Table: %s", err) 268 } 269 270 return nil 271 } 272 273 func resourceAwsVpcUpdate(d *schema.ResourceData, meta interface{}) error { 274 conn := meta.(*AWSClient).ec2conn 275 276 // Turn on partial mode 277 d.Partial(true) 278 vpcid := d.Id() 279 if d.HasChange("enable_dns_hostnames") { 280 val := d.Get("enable_dns_hostnames").(bool) 281 modifyOpts := &ec2.ModifyVpcAttributeInput{ 282 VpcId: &vpcid, 283 EnableDnsHostnames: &ec2.AttributeBooleanValue{ 284 Value: &val, 285 }, 286 } 287 288 log.Printf( 289 "[INFO] Modifying enable_dns_hostnames vpc attribute for %s: %s", 290 d.Id(), modifyOpts) 291 if _, err := conn.ModifyVpcAttribute(modifyOpts); err != nil { 292 return err 293 } 294 295 d.SetPartial("enable_dns_hostnames") 296 } 297 298 _, hasEnableDnsSupportOption := d.GetOk("enable_dns_support") 299 300 if !hasEnableDnsSupportOption || d.HasChange("enable_dns_support") { 301 val := d.Get("enable_dns_support").(bool) 302 modifyOpts := &ec2.ModifyVpcAttributeInput{ 303 VpcId: &vpcid, 304 EnableDnsSupport: &ec2.AttributeBooleanValue{ 305 Value: &val, 306 }, 307 } 308 309 log.Printf( 310 "[INFO] Modifying enable_dns_support vpc attribute for %s: %s", 311 d.Id(), modifyOpts) 312 if _, err := conn.ModifyVpcAttribute(modifyOpts); err != nil { 313 return err 314 } 315 316 d.SetPartial("enable_dns_support") 317 } 318 319 if d.HasChange("enable_classiclink") { 320 val := d.Get("enable_classiclink").(bool) 321 322 if val { 323 modifyOpts := &ec2.EnableVpcClassicLinkInput{ 324 VpcId: &vpcid, 325 } 326 log.Printf( 327 "[INFO] Modifying enable_classiclink vpc attribute for %s: %#v", 328 d.Id(), modifyOpts) 329 if _, err := conn.EnableVpcClassicLink(modifyOpts); err != nil { 330 return err 331 } 332 } else { 333 modifyOpts := &ec2.DisableVpcClassicLinkInput{ 334 VpcId: &vpcid, 335 } 336 log.Printf( 337 "[INFO] Modifying enable_classiclink vpc attribute for %s: %#v", 338 d.Id(), modifyOpts) 339 if _, err := conn.DisableVpcClassicLink(modifyOpts); err != nil { 340 return err 341 } 342 } 343 344 d.SetPartial("enable_classiclink") 345 } 346 347 if err := setTags(conn, d); err != nil { 348 return err 349 } else { 350 d.SetPartial("tags") 351 } 352 353 d.Partial(false) 354 return resourceAwsVpcRead(d, meta) 355 } 356 357 func resourceAwsVpcDelete(d *schema.ResourceData, meta interface{}) error { 358 conn := meta.(*AWSClient).ec2conn 359 vpcID := d.Id() 360 DeleteVpcOpts := &ec2.DeleteVpcInput{ 361 VpcId: &vpcID, 362 } 363 log.Printf("[INFO] Deleting VPC: %s", d.Id()) 364 365 return resource.Retry(5*time.Minute, func() *resource.RetryError { 366 _, err := conn.DeleteVpc(DeleteVpcOpts) 367 if err == nil { 368 return nil 369 } 370 371 ec2err, ok := err.(awserr.Error) 372 if !ok { 373 return resource.NonRetryableError(err) 374 } 375 376 switch ec2err.Code() { 377 case "InvalidVpcID.NotFound": 378 return nil 379 case "DependencyViolation": 380 return resource.RetryableError(err) 381 } 382 383 return resource.NonRetryableError(fmt.Errorf("Error deleting VPC: %s", err)) 384 }) 385 } 386 387 // VPCStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 388 // a VPC. 389 func VPCStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { 390 return func() (interface{}, string, error) { 391 DescribeVpcOpts := &ec2.DescribeVpcsInput{ 392 VpcIds: []*string{aws.String(id)}, 393 } 394 resp, err := conn.DescribeVpcs(DescribeVpcOpts) 395 if err != nil { 396 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpcID.NotFound" { 397 resp = nil 398 } else { 399 log.Printf("Error on VPCStateRefresh: %s", err) 400 return nil, "", err 401 } 402 } 403 404 if resp == nil { 405 // Sometimes AWS just has consistency issues and doesn't see 406 // our instance yet. Return an empty state. 407 return nil, "", nil 408 } 409 410 vpc := resp.Vpcs[0] 411 return vpc, *vpc.State, nil 412 } 413 } 414 415 func resourceAwsVpcSetDefaultNetworkAcl(conn *ec2.EC2, d *schema.ResourceData) error { 416 filter1 := &ec2.Filter{ 417 Name: aws.String("default"), 418 Values: []*string{aws.String("true")}, 419 } 420 filter2 := &ec2.Filter{ 421 Name: aws.String("vpc-id"), 422 Values: []*string{aws.String(d.Id())}, 423 } 424 DescribeNetworkACLOpts := &ec2.DescribeNetworkAclsInput{ 425 Filters: []*ec2.Filter{filter1, filter2}, 426 } 427 networkAclResp, err := conn.DescribeNetworkAcls(DescribeNetworkACLOpts) 428 429 if err != nil { 430 return err 431 } 432 if v := networkAclResp.NetworkAcls; len(v) > 0 { 433 d.Set("default_network_acl_id", v[0].NetworkAclId) 434 } 435 436 return nil 437 } 438 439 func resourceAwsVpcSetDefaultSecurityGroup(conn *ec2.EC2, d *schema.ResourceData) error { 440 filter1 := &ec2.Filter{ 441 Name: aws.String("group-name"), 442 Values: []*string{aws.String("default")}, 443 } 444 filter2 := &ec2.Filter{ 445 Name: aws.String("vpc-id"), 446 Values: []*string{aws.String(d.Id())}, 447 } 448 DescribeSgOpts := &ec2.DescribeSecurityGroupsInput{ 449 Filters: []*ec2.Filter{filter1, filter2}, 450 } 451 securityGroupResp, err := conn.DescribeSecurityGroups(DescribeSgOpts) 452 453 if err != nil { 454 return err 455 } 456 if v := securityGroupResp.SecurityGroups; len(v) > 0 { 457 d.Set("default_security_group_id", v[0].GroupId) 458 } 459 460 return nil 461 } 462 463 func resourceAwsVpcSetDefaultRouteTable(conn *ec2.EC2, d *schema.ResourceData) error { 464 filter1 := &ec2.Filter{ 465 Name: aws.String("association.main"), 466 Values: []*string{aws.String("true")}, 467 } 468 filter2 := &ec2.Filter{ 469 Name: aws.String("vpc-id"), 470 Values: []*string{aws.String(d.Id())}, 471 } 472 473 findOpts := &ec2.DescribeRouteTablesInput{ 474 Filters: []*ec2.Filter{filter1, filter2}, 475 } 476 477 resp, err := conn.DescribeRouteTables(findOpts) 478 if err != nil { 479 return err 480 } 481 482 if len(resp.RouteTables) < 1 || resp.RouteTables[0] == nil { 483 return fmt.Errorf("Default Route table not found") 484 } 485 486 // There Can Be Only 1 ... Default Route Table 487 d.Set("default_route_table_id", resp.RouteTables[0].RouteTableId) 488 489 return nil 490 } 491 492 func resourceAwsVpcInstanceImport( 493 d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { 494 d.Set("assign_generated_ipv6_cidr_block", false) 495 return []*schema.ResourceData{d}, nil 496 }