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