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