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