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