github.com/hobbeswalsh/terraform@v0.3.7-0.20150619183303-ad17cf55a0fa/builtin/providers/aws/resource_aws_instance.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "crypto/sha1" 6 "encoding/base64" 7 "encoding/hex" 8 "fmt" 9 "log" 10 "strings" 11 "time" 12 13 "github.com/aws/aws-sdk-go/aws" 14 "github.com/aws/aws-sdk-go/aws/awserr" 15 "github.com/aws/aws-sdk-go/aws/awsutil" 16 "github.com/aws/aws-sdk-go/service/ec2" 17 "github.com/hashicorp/terraform/helper/hashcode" 18 "github.com/hashicorp/terraform/helper/resource" 19 "github.com/hashicorp/terraform/helper/schema" 20 ) 21 22 func resourceAwsInstance() *schema.Resource { 23 return &schema.Resource{ 24 Create: resourceAwsInstanceCreate, 25 Read: resourceAwsInstanceRead, 26 Update: resourceAwsInstanceUpdate, 27 Delete: resourceAwsInstanceDelete, 28 29 SchemaVersion: 1, 30 MigrateState: resourceAwsInstanceMigrateState, 31 32 Schema: map[string]*schema.Schema{ 33 "ami": &schema.Schema{ 34 Type: schema.TypeString, 35 Required: true, 36 ForceNew: true, 37 }, 38 39 "associate_public_ip_address": &schema.Schema{ 40 Type: schema.TypeBool, 41 ForceNew: true, 42 Optional: true, 43 }, 44 45 "availability_zone": &schema.Schema{ 46 Type: schema.TypeString, 47 Optional: true, 48 Computed: true, 49 ForceNew: true, 50 }, 51 52 "placement_group": &schema.Schema{ 53 Type: schema.TypeString, 54 Optional: true, 55 Computed: true, 56 ForceNew: true, 57 }, 58 59 "instance_type": &schema.Schema{ 60 Type: schema.TypeString, 61 Required: true, 62 ForceNew: true, 63 }, 64 65 "key_name": &schema.Schema{ 66 Type: schema.TypeString, 67 Optional: true, 68 ForceNew: true, 69 Computed: true, 70 }, 71 72 "subnet_id": &schema.Schema{ 73 Type: schema.TypeString, 74 Optional: true, 75 Computed: true, 76 ForceNew: true, 77 }, 78 79 "private_ip": &schema.Schema{ 80 Type: schema.TypeString, 81 Optional: true, 82 ForceNew: true, 83 Computed: true, 84 }, 85 86 "source_dest_check": &schema.Schema{ 87 Type: schema.TypeBool, 88 Optional: true, 89 Default: true, 90 }, 91 92 "user_data": &schema.Schema{ 93 Type: schema.TypeString, 94 Optional: true, 95 ForceNew: true, 96 StateFunc: func(v interface{}) string { 97 switch v.(type) { 98 case string: 99 hash := sha1.Sum([]byte(v.(string))) 100 return hex.EncodeToString(hash[:]) 101 default: 102 return "" 103 } 104 }, 105 }, 106 107 "security_groups": &schema.Schema{ 108 Type: schema.TypeSet, 109 Optional: true, 110 Computed: true, 111 ForceNew: true, 112 Elem: &schema.Schema{Type: schema.TypeString}, 113 Set: schema.HashString, 114 }, 115 116 "vpc_security_group_ids": &schema.Schema{ 117 Type: schema.TypeSet, 118 Optional: true, 119 Computed: true, 120 Elem: &schema.Schema{Type: schema.TypeString}, 121 Set: func(v interface{}) int { 122 return hashcode.String(v.(string)) 123 }, 124 }, 125 126 "public_dns": &schema.Schema{ 127 Type: schema.TypeString, 128 Computed: true, 129 }, 130 131 "public_ip": &schema.Schema{ 132 Type: schema.TypeString, 133 Computed: true, 134 }, 135 136 "private_dns": &schema.Schema{ 137 Type: schema.TypeString, 138 Computed: true, 139 }, 140 141 "ebs_optimized": &schema.Schema{ 142 Type: schema.TypeBool, 143 Optional: true, 144 }, 145 146 "disable_api_termination": &schema.Schema{ 147 Type: schema.TypeBool, 148 Optional: true, 149 }, 150 151 "iam_instance_profile": &schema.Schema{ 152 Type: schema.TypeString, 153 ForceNew: true, 154 Optional: true, 155 }, 156 157 "tenancy": &schema.Schema{ 158 Type: schema.TypeString, 159 Optional: true, 160 Computed: true, 161 ForceNew: true, 162 }, 163 164 "tags": tagsSchema(), 165 166 "block_device": &schema.Schema{ 167 Type: schema.TypeMap, 168 Optional: true, 169 Removed: "Split out into three sub-types; see Changelog and Docs", 170 }, 171 172 "ebs_block_device": &schema.Schema{ 173 Type: schema.TypeSet, 174 Optional: true, 175 Computed: true, 176 Elem: &schema.Resource{ 177 Schema: map[string]*schema.Schema{ 178 "delete_on_termination": &schema.Schema{ 179 Type: schema.TypeBool, 180 Optional: true, 181 Default: true, 182 ForceNew: true, 183 }, 184 185 "device_name": &schema.Schema{ 186 Type: schema.TypeString, 187 Required: true, 188 ForceNew: true, 189 }, 190 191 "encrypted": &schema.Schema{ 192 Type: schema.TypeBool, 193 Optional: true, 194 Computed: true, 195 ForceNew: true, 196 }, 197 198 "iops": &schema.Schema{ 199 Type: schema.TypeInt, 200 Optional: true, 201 Computed: true, 202 ForceNew: true, 203 }, 204 205 "snapshot_id": &schema.Schema{ 206 Type: schema.TypeString, 207 Optional: true, 208 Computed: true, 209 ForceNew: true, 210 }, 211 212 "volume_size": &schema.Schema{ 213 Type: schema.TypeInt, 214 Optional: true, 215 Computed: true, 216 ForceNew: true, 217 }, 218 219 "volume_type": &schema.Schema{ 220 Type: schema.TypeString, 221 Optional: true, 222 Computed: true, 223 ForceNew: true, 224 }, 225 }, 226 }, 227 Set: func(v interface{}) int { 228 var buf bytes.Buffer 229 m := v.(map[string]interface{}) 230 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 231 buf.WriteString(fmt.Sprintf("%s-", m["snapshot_id"].(string))) 232 return hashcode.String(buf.String()) 233 }, 234 }, 235 236 "ephemeral_block_device": &schema.Schema{ 237 Type: schema.TypeSet, 238 Optional: true, 239 Computed: true, 240 ForceNew: true, 241 Elem: &schema.Resource{ 242 Schema: map[string]*schema.Schema{ 243 "device_name": &schema.Schema{ 244 Type: schema.TypeString, 245 Required: true, 246 }, 247 248 "virtual_name": &schema.Schema{ 249 Type: schema.TypeString, 250 Required: true, 251 }, 252 }, 253 }, 254 Set: func(v interface{}) int { 255 var buf bytes.Buffer 256 m := v.(map[string]interface{}) 257 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 258 buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string))) 259 return hashcode.String(buf.String()) 260 }, 261 }, 262 263 "root_block_device": &schema.Schema{ 264 // TODO: This is a set because we don't support singleton 265 // sub-resources today. We'll enforce that the set only ever has 266 // length zero or one below. When TF gains support for 267 // sub-resources this can be converted. 268 Type: schema.TypeSet, 269 Optional: true, 270 Computed: true, 271 Elem: &schema.Resource{ 272 // "You can only modify the volume size, volume type, and Delete on 273 // Termination flag on the block device mapping entry for the root 274 // device volume." - bit.ly/ec2bdmap 275 Schema: map[string]*schema.Schema{ 276 "delete_on_termination": &schema.Schema{ 277 Type: schema.TypeBool, 278 Optional: true, 279 Default: true, 280 ForceNew: true, 281 }, 282 283 "iops": &schema.Schema{ 284 Type: schema.TypeInt, 285 Optional: true, 286 Computed: true, 287 ForceNew: true, 288 }, 289 290 "volume_size": &schema.Schema{ 291 Type: schema.TypeInt, 292 Optional: true, 293 Computed: true, 294 ForceNew: true, 295 }, 296 297 "volume_type": &schema.Schema{ 298 Type: schema.TypeString, 299 Optional: true, 300 Computed: true, 301 ForceNew: true, 302 }, 303 }, 304 }, 305 Set: func(v interface{}) int { 306 // there can be only one root device; no need to hash anything 307 return 0 308 }, 309 }, 310 }, 311 } 312 } 313 314 func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error { 315 conn := meta.(*AWSClient).ec2conn 316 317 instanceOpts, err := buildAwsInstanceOpts(d, meta) 318 if err != nil { 319 return err 320 } 321 322 // Build the creation struct 323 runOpts := &ec2.RunInstancesInput{ 324 BlockDeviceMappings: instanceOpts.BlockDeviceMappings, 325 DisableAPITermination: instanceOpts.DisableAPITermination, 326 EBSOptimized: instanceOpts.EBSOptimized, 327 IAMInstanceProfile: instanceOpts.IAMInstanceProfile, 328 ImageID: instanceOpts.ImageID, 329 InstanceType: instanceOpts.InstanceType, 330 KeyName: instanceOpts.KeyName, 331 MaxCount: aws.Long(int64(1)), 332 MinCount: aws.Long(int64(1)), 333 NetworkInterfaces: instanceOpts.NetworkInterfaces, 334 Placement: instanceOpts.Placement, 335 PrivateIPAddress: instanceOpts.PrivateIPAddress, 336 SecurityGroupIDs: instanceOpts.SecurityGroupIDs, 337 SecurityGroups: instanceOpts.SecurityGroups, 338 SubnetID: instanceOpts.SubnetID, 339 UserData: instanceOpts.UserData64, 340 } 341 342 // Create the instance 343 log.Printf("[DEBUG] Run configuration: %s", awsutil.StringValue(runOpts)) 344 345 var runResp *ec2.Reservation 346 for i := 0; i < 5; i++ { 347 runResp, err = conn.RunInstances(runOpts) 348 if awsErr, ok := err.(awserr.Error); ok { 349 // IAM profiles can take ~10 seconds to propagate in AWS: 350 // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console 351 if awsErr.Code() == "InvalidParameterValue" && strings.Contains(awsErr.Message(), "Invalid IAM Instance Profile") { 352 log.Printf("[DEBUG] Invalid IAM Instance Profile referenced, retrying...") 353 time.Sleep(2 * time.Second) 354 continue 355 } 356 } 357 break 358 } 359 if err != nil { 360 return fmt.Errorf("Error launching source instance: %s", err) 361 } 362 363 instance := runResp.Instances[0] 364 log.Printf("[INFO] Instance ID: %s", *instance.InstanceID) 365 366 // Store the resulting ID so we can look this up later 367 d.SetId(*instance.InstanceID) 368 369 // Wait for the instance to become running so we can get some attributes 370 // that aren't available until later. 371 log.Printf( 372 "[DEBUG] Waiting for instance (%s) to become running", 373 *instance.InstanceID) 374 375 stateConf := &resource.StateChangeConf{ 376 Pending: []string{"pending"}, 377 Target: "running", 378 Refresh: InstanceStateRefreshFunc(conn, *instance.InstanceID), 379 Timeout: 10 * time.Minute, 380 Delay: 10 * time.Second, 381 MinTimeout: 3 * time.Second, 382 } 383 384 instanceRaw, err := stateConf.WaitForState() 385 if err != nil { 386 return fmt.Errorf( 387 "Error waiting for instance (%s) to become ready: %s", 388 *instance.InstanceID, err) 389 } 390 391 instance = instanceRaw.(*ec2.Instance) 392 393 // Initialize the connection info 394 if instance.PublicIPAddress != nil { 395 d.SetConnInfo(map[string]string{ 396 "type": "ssh", 397 "host": *instance.PublicIPAddress, 398 }) 399 } else if instance.PrivateIPAddress != nil { 400 d.SetConnInfo(map[string]string{ 401 "type": "ssh", 402 "host": *instance.PrivateIPAddress, 403 }) 404 } 405 406 // Set our attributes 407 if err := resourceAwsInstanceRead(d, meta); err != nil { 408 return err 409 } 410 411 // Update if we need to 412 return resourceAwsInstanceUpdate(d, meta) 413 } 414 415 func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { 416 conn := meta.(*AWSClient).ec2conn 417 418 resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{ 419 InstanceIDs: []*string{aws.String(d.Id())}, 420 }) 421 if err != nil { 422 // If the instance was not found, return nil so that we can show 423 // that the instance is gone. 424 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidInstanceID.NotFound" { 425 d.SetId("") 426 return nil 427 } 428 429 // Some other error, report it 430 return err 431 } 432 433 // If nothing was found, then return no state 434 if len(resp.Reservations) == 0 { 435 d.SetId("") 436 return nil 437 } 438 439 instance := resp.Reservations[0].Instances[0] 440 441 // If the instance is terminated, then it is gone 442 if *instance.State.Name == "terminated" { 443 d.SetId("") 444 return nil 445 } 446 447 if instance.Placement != nil { 448 d.Set("availability_zone", instance.Placement.AvailabilityZone) 449 } 450 if instance.Placement.Tenancy != nil { 451 d.Set("tenancy", instance.Placement.Tenancy) 452 } 453 454 d.Set("key_name", instance.KeyName) 455 d.Set("public_dns", instance.PublicDNSName) 456 d.Set("public_ip", instance.PublicIPAddress) 457 d.Set("private_dns", instance.PrivateDNSName) 458 d.Set("private_ip", instance.PrivateIPAddress) 459 if len(instance.NetworkInterfaces) > 0 { 460 d.Set("subnet_id", instance.NetworkInterfaces[0].SubnetID) 461 } else { 462 d.Set("subnet_id", instance.SubnetID) 463 } 464 d.Set("ebs_optimized", instance.EBSOptimized) 465 d.Set("tags", tagsToMap(instance.Tags)) 466 467 // Determine whether we're referring to security groups with 468 // IDs or names. We use a heuristic to figure this out. By default, 469 // we use IDs if we're in a VPC. However, if we previously had an 470 // all-name list of security groups, we use names. Or, if we had any 471 // IDs, we use IDs. 472 useID := instance.SubnetID != nil && *instance.SubnetID != "" 473 if v := d.Get("security_groups"); v != nil { 474 match := useID 475 sgs := v.(*schema.Set).List() 476 if len(sgs) > 0 { 477 match = false 478 for _, v := range v.(*schema.Set).List() { 479 if strings.HasPrefix(v.(string), "sg-") { 480 match = true 481 break 482 } 483 } 484 } 485 486 useID = match 487 } 488 489 // Build up the security groups 490 sgs := make([]string, 0, len(instance.SecurityGroups)) 491 if useID { 492 for _, sg := range instance.SecurityGroups { 493 sgs = append(sgs, *sg.GroupID) 494 } 495 log.Printf("[DEBUG] Setting Security Group IDs: %#v", sgs) 496 if err := d.Set("vpc_security_group_ids", sgs); err != nil { 497 return err 498 } 499 } else { 500 for _, sg := range instance.SecurityGroups { 501 sgs = append(sgs, *sg.GroupName) 502 } 503 log.Printf("[DEBUG] Setting Security Group Names: %#v", sgs) 504 if err := d.Set("security_groups", sgs); err != nil { 505 return err 506 } 507 } 508 509 if err := readBlockDevices(d, instance, conn); err != nil { 510 return err 511 } 512 513 return nil 514 } 515 516 func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error { 517 conn := meta.(*AWSClient).ec2conn 518 519 d.Partial(true) 520 if err := setTags(conn, d); err != nil { 521 return err 522 } else { 523 d.SetPartial("tags") 524 } 525 526 // SourceDestCheck can only be set on VPC instances 527 if d.Get("subnet_id").(string) != "" { 528 log.Printf("[INFO] Modifying instance %s", d.Id()) 529 _, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{ 530 InstanceID: aws.String(d.Id()), 531 SourceDestCheck: &ec2.AttributeBooleanValue{ 532 Value: aws.Boolean(d.Get("source_dest_check").(bool)), 533 }, 534 }) 535 if err != nil { 536 return err 537 } 538 } 539 540 if d.HasChange("vpc_security_group_ids") { 541 var groups []*string 542 if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 { 543 for _, v := range v.List() { 544 groups = append(groups, aws.String(v.(string))) 545 } 546 } 547 _, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{ 548 InstanceID: aws.String(d.Id()), 549 Groups: groups, 550 }) 551 if err != nil { 552 return err 553 } 554 } 555 556 if d.HasChange("disable_api_termination") { 557 _, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{ 558 InstanceID: aws.String(d.Id()), 559 DisableAPITermination: &ec2.AttributeBooleanValue{ 560 Value: aws.Boolean(d.Get("disable_api_termination").(bool)), 561 }, 562 }) 563 if err != nil { 564 return err 565 } 566 } 567 568 // TODO(mitchellh): wait for the attributes we modified to 569 // persist the change... 570 571 d.Partial(false) 572 573 return resourceAwsInstanceRead(d, meta) 574 } 575 576 func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error { 577 conn := meta.(*AWSClient).ec2conn 578 579 if err := awsTerminateInstance(conn, d.Id()); err != nil { 580 return err 581 } 582 583 d.SetId("") 584 return nil 585 } 586 587 // InstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 588 // an EC2 instance. 589 func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc { 590 return func() (interface{}, string, error) { 591 resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{ 592 InstanceIDs: []*string{aws.String(instanceID)}, 593 }) 594 if err != nil { 595 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidInstanceID.NotFound" { 596 // Set this to nil as if we didn't find anything. 597 resp = nil 598 } else { 599 log.Printf("Error on InstanceStateRefresh: %s", err) 600 return nil, "", err 601 } 602 } 603 604 if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 { 605 // Sometimes AWS just has consistency issues and doesn't see 606 // our instance yet. Return an empty state. 607 return nil, "", nil 608 } 609 610 i := resp.Reservations[0].Instances[0] 611 return i, *i.State.Name, nil 612 } 613 } 614 615 func readBlockDevices(d *schema.ResourceData, instance *ec2.Instance, conn *ec2.EC2) error { 616 ibds, err := readBlockDevicesFromInstance(instance, conn) 617 if err != nil { 618 return err 619 } 620 621 if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil { 622 return err 623 } 624 if ibds["root"] != nil { 625 if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil { 626 return err 627 } 628 } 629 630 return nil 631 } 632 633 func readBlockDevicesFromInstance(instance *ec2.Instance, conn *ec2.EC2) (map[string]interface{}, error) { 634 blockDevices := make(map[string]interface{}) 635 blockDevices["ebs"] = make([]map[string]interface{}, 0) 636 blockDevices["root"] = nil 637 638 instanceBlockDevices := make(map[string]*ec2.InstanceBlockDeviceMapping) 639 for _, bd := range instance.BlockDeviceMappings { 640 if bd.EBS != nil { 641 instanceBlockDevices[*(bd.EBS.VolumeID)] = bd 642 } 643 } 644 645 if len(instanceBlockDevices) == 0 { 646 return nil, nil 647 } 648 649 volIDs := make([]*string, 0, len(instanceBlockDevices)) 650 for volID := range instanceBlockDevices { 651 volIDs = append(volIDs, aws.String(volID)) 652 } 653 654 // Need to call DescribeVolumes to get volume_size and volume_type for each 655 // EBS block device 656 volResp, err := conn.DescribeVolumes(&ec2.DescribeVolumesInput{ 657 VolumeIDs: volIDs, 658 }) 659 if err != nil { 660 return nil, err 661 } 662 663 for _, vol := range volResp.Volumes { 664 instanceBd := instanceBlockDevices[*vol.VolumeID] 665 bd := make(map[string]interface{}) 666 667 if instanceBd.EBS != nil && instanceBd.EBS.DeleteOnTermination != nil { 668 bd["delete_on_termination"] = *instanceBd.EBS.DeleteOnTermination 669 } 670 if vol.Size != nil { 671 bd["volume_size"] = *vol.Size 672 } 673 if vol.VolumeType != nil { 674 bd["volume_type"] = *vol.VolumeType 675 } 676 if vol.IOPS != nil { 677 bd["iops"] = *vol.IOPS 678 } 679 680 if blockDeviceIsRoot(instanceBd, instance) { 681 blockDevices["root"] = bd 682 } else { 683 if instanceBd.DeviceName != nil { 684 bd["device_name"] = *instanceBd.DeviceName 685 } 686 if vol.Encrypted != nil { 687 bd["encrypted"] = *vol.Encrypted 688 } 689 if vol.SnapshotID != nil { 690 bd["snapshot_id"] = *vol.SnapshotID 691 } 692 693 blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd) 694 } 695 } 696 697 return blockDevices, nil 698 } 699 700 func blockDeviceIsRoot(bd *ec2.InstanceBlockDeviceMapping, instance *ec2.Instance) bool { 701 return (bd.DeviceName != nil && 702 instance.RootDeviceName != nil && 703 *bd.DeviceName == *instance.RootDeviceName) 704 } 705 706 func fetchRootDeviceName(ami string, conn *ec2.EC2) (*string, error) { 707 if ami == "" { 708 return nil, fmt.Errorf("Cannot fetch root device name for blank AMI ID.") 709 } 710 711 log.Printf("[DEBUG] Describing AMI %q to get root block device name", ami) 712 req := &ec2.DescribeImagesInput{ImageIDs: []*string{aws.String(ami)}} 713 if res, err := conn.DescribeImages(req); err == nil { 714 if len(res.Images) == 1 { 715 return res.Images[0].RootDeviceName, nil 716 } else if len(res.Images) == 0 { 717 return nil, nil 718 } else { 719 return nil, fmt.Errorf("Expected 1 AMI for ID: %s, got: %#v", ami, res.Images) 720 } 721 } else { 722 return nil, err 723 } 724 } 725 726 func readBlockDeviceMappingsFromConfig( 727 d *schema.ResourceData, conn *ec2.EC2) ([]*ec2.BlockDeviceMapping, error) { 728 blockDevices := make([]*ec2.BlockDeviceMapping, 0) 729 730 if v, ok := d.GetOk("ebs_block_device"); ok { 731 vL := v.(*schema.Set).List() 732 for _, v := range vL { 733 bd := v.(map[string]interface{}) 734 ebs := &ec2.EBSBlockDevice{ 735 DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)), 736 } 737 738 if v, ok := bd["snapshot_id"].(string); ok && v != "" { 739 ebs.SnapshotID = aws.String(v) 740 } 741 742 if v, ok := bd["encrypted"].(bool); ok && v { 743 ebs.Encrypted = aws.Boolean(v) 744 } 745 746 if v, ok := bd["volume_size"].(int); ok && v != 0 { 747 ebs.VolumeSize = aws.Long(int64(v)) 748 } 749 750 if v, ok := bd["volume_type"].(string); ok && v != "" { 751 ebs.VolumeType = aws.String(v) 752 } 753 754 if v, ok := bd["iops"].(int); ok && v > 0 { 755 ebs.IOPS = aws.Long(int64(v)) 756 } 757 758 blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{ 759 DeviceName: aws.String(bd["device_name"].(string)), 760 EBS: ebs, 761 }) 762 } 763 } 764 765 if v, ok := d.GetOk("ephemeral_block_device"); ok { 766 vL := v.(*schema.Set).List() 767 for _, v := range vL { 768 bd := v.(map[string]interface{}) 769 blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{ 770 DeviceName: aws.String(bd["device_name"].(string)), 771 VirtualName: aws.String(bd["virtual_name"].(string)), 772 }) 773 } 774 } 775 776 if v, ok := d.GetOk("root_block_device"); ok { 777 vL := v.(*schema.Set).List() 778 if len(vL) > 1 { 779 return nil, fmt.Errorf("Cannot specify more than one root_block_device.") 780 } 781 for _, v := range vL { 782 bd := v.(map[string]interface{}) 783 ebs := &ec2.EBSBlockDevice{ 784 DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)), 785 } 786 787 if v, ok := bd["volume_size"].(int); ok && v != 0 { 788 ebs.VolumeSize = aws.Long(int64(v)) 789 } 790 791 if v, ok := bd["volume_type"].(string); ok && v != "" { 792 ebs.VolumeType = aws.String(v) 793 } 794 795 if v, ok := bd["iops"].(int); ok && v > 0 { 796 ebs.IOPS = aws.Long(int64(v)) 797 } 798 799 if dn, err := fetchRootDeviceName(d.Get("ami").(string), conn); err == nil { 800 if dn == nil { 801 return nil, fmt.Errorf( 802 "Expected 1 AMI for ID: %s, got none", 803 d.Get("ami").(string)) 804 } 805 806 blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{ 807 DeviceName: dn, 808 EBS: ebs, 809 }) 810 } else { 811 return nil, err 812 } 813 } 814 } 815 816 return blockDevices, nil 817 } 818 819 type awsInstanceOpts struct { 820 BlockDeviceMappings []*ec2.BlockDeviceMapping 821 DisableAPITermination *bool 822 EBSOptimized *bool 823 IAMInstanceProfile *ec2.IAMInstanceProfileSpecification 824 ImageID *string 825 InstanceType *string 826 KeyName *string 827 NetworkInterfaces []*ec2.InstanceNetworkInterfaceSpecification 828 Placement *ec2.Placement 829 PrivateIPAddress *string 830 SecurityGroupIDs []*string 831 SecurityGroups []*string 832 SpotPlacement *ec2.SpotPlacement 833 SubnetID *string 834 UserData64 *string 835 } 836 837 func buildAwsInstanceOpts( 838 d *schema.ResourceData, meta interface{}) (*awsInstanceOpts, error) { 839 conn := meta.(*AWSClient).ec2conn 840 841 opts := &awsInstanceOpts{ 842 DisableAPITermination: aws.Boolean(d.Get("disable_api_termination").(bool)), 843 EBSOptimized: aws.Boolean(d.Get("ebs_optimized").(bool)), 844 ImageID: aws.String(d.Get("ami").(string)), 845 InstanceType: aws.String(d.Get("instance_type").(string)), 846 } 847 848 opts.IAMInstanceProfile = &ec2.IAMInstanceProfileSpecification{ 849 Name: aws.String(d.Get("iam_instance_profile").(string)), 850 } 851 852 opts.UserData64 = aws.String( 853 base64.StdEncoding.EncodeToString([]byte(d.Get("user_data").(string)))) 854 855 // check for non-default Subnet, and cast it to a String 856 subnet, hasSubnet := d.GetOk("subnet_id") 857 subnetID := subnet.(string) 858 859 // Placement is used for aws_instance; SpotPlacement is used for 860 // aws_spot_instance_request. They represent the same data. :-| 861 opts.Placement = &ec2.Placement{ 862 AvailabilityZone: aws.String(d.Get("availability_zone").(string)), 863 GroupName: aws.String(d.Get("placement_group").(string)), 864 } 865 866 opts.SpotPlacement = &ec2.SpotPlacement{ 867 AvailabilityZone: aws.String(d.Get("availability_zone").(string)), 868 GroupName: aws.String(d.Get("placement_group").(string)), 869 } 870 871 if v := d.Get("tenancy").(string); v != "" { 872 opts.Placement.Tenancy = aws.String(v) 873 } 874 875 associatePublicIPAddress := d.Get("associate_public_ip_address").(bool) 876 877 var groups []*string 878 if v := d.Get("security_groups"); v != nil { 879 // Security group names. 880 // For a nondefault VPC, you must use security group IDs instead. 881 // See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html 882 sgs := v.(*schema.Set).List() 883 if len(sgs) > 0 && hasSubnet { 884 log.Printf("[WARN] Deprecated. Attempting to use 'security_groups' within a VPC instance. Use 'vpc_security_group_ids' instead.") 885 } 886 for _, v := range sgs { 887 str := v.(string) 888 groups = append(groups, aws.String(str)) 889 } 890 } 891 892 if hasSubnet && associatePublicIPAddress { 893 // If we have a non-default VPC / Subnet specified, we can flag 894 // AssociatePublicIpAddress to get a Public IP assigned. By default these are not provided. 895 // You cannot specify both SubnetId and the NetworkInterface.0.* parameters though, otherwise 896 // you get: Network interfaces and an instance-level subnet ID may not be specified on the same request 897 // You also need to attach Security Groups to the NetworkInterface instead of the instance, 898 // to avoid: Network interfaces and an instance-level security groups may not be specified on 899 // the same request 900 ni := &ec2.InstanceNetworkInterfaceSpecification{ 901 AssociatePublicIPAddress: aws.Boolean(associatePublicIPAddress), 902 DeviceIndex: aws.Long(int64(0)), 903 SubnetID: aws.String(subnetID), 904 Groups: groups, 905 } 906 907 if v, ok := d.GetOk("private_ip"); ok { 908 ni.PrivateIPAddress = aws.String(v.(string)) 909 } 910 911 if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 { 912 for _, v := range v.List() { 913 ni.Groups = append(ni.Groups, aws.String(v.(string))) 914 } 915 } 916 917 opts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{ni} 918 } else { 919 if subnetID != "" { 920 opts.SubnetID = aws.String(subnetID) 921 } 922 923 if v, ok := d.GetOk("private_ip"); ok { 924 opts.PrivateIPAddress = aws.String(v.(string)) 925 } 926 if opts.SubnetID != nil && 927 *opts.SubnetID != "" { 928 opts.SecurityGroupIDs = groups 929 } else { 930 opts.SecurityGroups = groups 931 } 932 933 if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 { 934 for _, v := range v.List() { 935 opts.SecurityGroupIDs = append(opts.SecurityGroupIDs, aws.String(v.(string))) 936 } 937 } 938 } 939 940 if v, ok := d.GetOk("key_name"); ok { 941 opts.KeyName = aws.String(v.(string)) 942 } 943 944 blockDevices, err := readBlockDeviceMappingsFromConfig(d, conn) 945 if err != nil { 946 return nil, err 947 } 948 if len(blockDevices) > 0 { 949 opts.BlockDeviceMappings = blockDevices 950 } 951 952 return opts, nil 953 } 954 955 func awsTerminateInstance(conn *ec2.EC2, id string) error { 956 log.Printf("[INFO] Terminating instance: %s", id) 957 req := &ec2.TerminateInstancesInput{ 958 InstanceIDs: []*string{aws.String(id)}, 959 } 960 if _, err := conn.TerminateInstances(req); err != nil { 961 return fmt.Errorf("Error terminating instance: %s", err) 962 } 963 964 log.Printf("[DEBUG] Waiting for instance (%s) to become terminated", id) 965 966 stateConf := &resource.StateChangeConf{ 967 Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"}, 968 Target: "terminated", 969 Refresh: InstanceStateRefreshFunc(conn, id), 970 Timeout: 10 * time.Minute, 971 Delay: 10 * time.Second, 972 MinTimeout: 3 * time.Second, 973 } 974 975 _, err := stateConf.WaitForState() 976 if err != nil { 977 return fmt.Errorf( 978 "Error waiting for instance (%s) to terminate: %s", id, err) 979 } 980 981 return nil 982 }