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