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