github.com/alexissmirnov/terraform@v0.4.3-0.20150423153700-1ef9731a2f14/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: func(v interface{}) int { 111 return hashcode.String(v.(string)) 112 }, 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 "iam_instance_profile": &schema.Schema{ 146 Type: schema.TypeString, 147 ForceNew: true, 148 Optional: true, 149 }, 150 151 "tenancy": &schema.Schema{ 152 Type: schema.TypeString, 153 Optional: true, 154 Computed: true, 155 ForceNew: true, 156 }, 157 158 "tags": tagsSchema(), 159 160 "block_device": &schema.Schema{ 161 Type: schema.TypeMap, 162 Optional: true, 163 Removed: "Split out into three sub-types; see Changelog and Docs", 164 }, 165 166 "ebs_block_device": &schema.Schema{ 167 Type: schema.TypeSet, 168 Optional: true, 169 Computed: true, 170 Elem: &schema.Resource{ 171 Schema: map[string]*schema.Schema{ 172 "delete_on_termination": &schema.Schema{ 173 Type: schema.TypeBool, 174 Optional: true, 175 Default: true, 176 ForceNew: true, 177 }, 178 179 "device_name": &schema.Schema{ 180 Type: schema.TypeString, 181 Required: true, 182 ForceNew: true, 183 }, 184 185 "encrypted": &schema.Schema{ 186 Type: schema.TypeBool, 187 Optional: true, 188 Computed: true, 189 ForceNew: true, 190 }, 191 192 "iops": &schema.Schema{ 193 Type: schema.TypeInt, 194 Optional: true, 195 Computed: true, 196 ForceNew: true, 197 }, 198 199 "snapshot_id": &schema.Schema{ 200 Type: schema.TypeString, 201 Optional: true, 202 Computed: true, 203 ForceNew: true, 204 }, 205 206 "volume_size": &schema.Schema{ 207 Type: schema.TypeInt, 208 Optional: true, 209 Computed: true, 210 ForceNew: true, 211 }, 212 213 "volume_type": &schema.Schema{ 214 Type: schema.TypeString, 215 Optional: true, 216 Computed: true, 217 ForceNew: true, 218 }, 219 }, 220 }, 221 Set: func(v interface{}) int { 222 var buf bytes.Buffer 223 m := v.(map[string]interface{}) 224 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 225 buf.WriteString(fmt.Sprintf("%s-", m["snapshot_id"].(string))) 226 return hashcode.String(buf.String()) 227 }, 228 }, 229 230 "ephemeral_block_device": &schema.Schema{ 231 Type: schema.TypeSet, 232 Optional: true, 233 Computed: true, 234 ForceNew: true, 235 Elem: &schema.Resource{ 236 Schema: map[string]*schema.Schema{ 237 "device_name": &schema.Schema{ 238 Type: schema.TypeString, 239 Required: true, 240 }, 241 242 "virtual_name": &schema.Schema{ 243 Type: schema.TypeString, 244 Required: true, 245 }, 246 }, 247 }, 248 Set: func(v interface{}) int { 249 var buf bytes.Buffer 250 m := v.(map[string]interface{}) 251 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 252 buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string))) 253 return hashcode.String(buf.String()) 254 }, 255 }, 256 257 "root_block_device": &schema.Schema{ 258 // TODO: This is a set because we don't support singleton 259 // sub-resources today. We'll enforce that the set only ever has 260 // length zero or one below. When TF gains support for 261 // sub-resources this can be converted. 262 Type: schema.TypeSet, 263 Optional: true, 264 Computed: true, 265 Elem: &schema.Resource{ 266 // "You can only modify the volume size, volume type, and Delete on 267 // Termination flag on the block device mapping entry for the root 268 // device volume." - bit.ly/ec2bdmap 269 Schema: map[string]*schema.Schema{ 270 "delete_on_termination": &schema.Schema{ 271 Type: schema.TypeBool, 272 Optional: true, 273 Default: true, 274 ForceNew: true, 275 }, 276 277 "iops": &schema.Schema{ 278 Type: schema.TypeInt, 279 Optional: true, 280 Computed: true, 281 ForceNew: true, 282 }, 283 284 "volume_size": &schema.Schema{ 285 Type: schema.TypeInt, 286 Optional: true, 287 Computed: true, 288 ForceNew: true, 289 }, 290 291 "volume_type": &schema.Schema{ 292 Type: schema.TypeString, 293 Optional: true, 294 Computed: true, 295 ForceNew: true, 296 }, 297 }, 298 }, 299 Set: func(v interface{}) int { 300 // there can be only one root device; no need to hash anything 301 return 0 302 }, 303 }, 304 }, 305 } 306 } 307 308 func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error { 309 conn := meta.(*AWSClient).ec2conn 310 311 // Figure out user data 312 userData := "" 313 if v := d.Get("user_data"); v != nil { 314 userData = base64.StdEncoding.EncodeToString([]byte(v.(string))) 315 } 316 317 // check for non-default Subnet, and cast it to a String 318 var hasSubnet bool 319 subnet, hasSubnet := d.GetOk("subnet_id") 320 subnetID := subnet.(string) 321 322 placement := &ec2.Placement{ 323 AvailabilityZone: aws.String(d.Get("availability_zone").(string)), 324 GroupName: aws.String(d.Get("placement_group").(string)), 325 } 326 327 if hasSubnet { 328 // Tenancy is only valid inside a VPC 329 // See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_Placement.html 330 if v := d.Get("tenancy").(string); v != "" { 331 placement.Tenancy = aws.String(v) 332 } 333 } 334 335 iam := &ec2.IAMInstanceProfileSpecification{ 336 Name: aws.String(d.Get("iam_instance_profile").(string)), 337 } 338 339 // Build the creation struct 340 runOpts := &ec2.RunInstancesInput{ 341 ImageID: aws.String(d.Get("ami").(string)), 342 Placement: placement, 343 InstanceType: aws.String(d.Get("instance_type").(string)), 344 MaxCount: aws.Long(int64(1)), 345 MinCount: aws.Long(int64(1)), 346 UserData: aws.String(userData), 347 EBSOptimized: aws.Boolean(d.Get("ebs_optimized").(bool)), 348 IAMInstanceProfile: iam, 349 } 350 351 associatePublicIPAddress := false 352 if v := d.Get("associate_public_ip_address"); v != nil { 353 associatePublicIPAddress = v.(bool) 354 } 355 356 var groups []*string 357 if v := d.Get("security_groups"); v != nil { 358 // Security group names. 359 // For a nondefault VPC, you must use security group IDs instead. 360 // See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html 361 if hasSubnet { 362 log.Printf("[WARN] Deprecated. Attempting to use 'security_groups' within a VPC instance. Use 'vpc_security_group_ids' instead.") 363 } 364 for _, v := range v.(*schema.Set).List() { 365 str := v.(string) 366 groups = append(groups, aws.String(str)) 367 } 368 } 369 370 if hasSubnet && associatePublicIPAddress { 371 // If we have a non-default VPC / Subnet specified, we can flag 372 // AssociatePublicIpAddress to get a Public IP assigned. By default these are not provided. 373 // You cannot specify both SubnetId and the NetworkInterface.0.* parameters though, otherwise 374 // you get: Network interfaces and an instance-level subnet ID may not be specified on the same request 375 // You also need to attach Security Groups to the NetworkInterface instead of the instance, 376 // to avoid: Network interfaces and an instance-level security groups may not be specified on 377 // the same request 378 ni := &ec2.InstanceNetworkInterfaceSpecification{ 379 AssociatePublicIPAddress: aws.Boolean(associatePublicIPAddress), 380 DeviceIndex: aws.Long(int64(0)), 381 SubnetID: aws.String(subnetID), 382 Groups: groups, 383 } 384 385 if v, ok := d.GetOk("private_ip"); ok { 386 ni.PrivateIPAddress = aws.String(v.(string)) 387 } 388 389 if v := d.Get("vpc_security_group_ids"); v != nil { 390 for _, v := range v.(*schema.Set).List() { 391 ni.Groups = append(ni.Groups, aws.String(v.(string))) 392 } 393 } 394 395 runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{ni} 396 } else { 397 if subnetID != "" { 398 runOpts.SubnetID = aws.String(subnetID) 399 } 400 401 if v, ok := d.GetOk("private_ip"); ok { 402 runOpts.PrivateIPAddress = aws.String(v.(string)) 403 } 404 if runOpts.SubnetID != nil && 405 *runOpts.SubnetID != "" { 406 runOpts.SecurityGroupIDs = groups 407 } else { 408 runOpts.SecurityGroups = groups 409 } 410 411 if v := d.Get("vpc_security_group_ids"); v != nil { 412 for _, v := range v.(*schema.Set).List() { 413 runOpts.SecurityGroupIDs = append(runOpts.SecurityGroupIDs, aws.String(v.(string))) 414 } 415 } 416 } 417 418 if v, ok := d.GetOk("key_name"); ok { 419 runOpts.KeyName = aws.String(v.(string)) 420 } 421 422 blockDevices := make([]*ec2.BlockDeviceMapping, 0) 423 424 if v, ok := d.GetOk("ebs_block_device"); ok { 425 vL := v.(*schema.Set).List() 426 for _, v := range vL { 427 bd := v.(map[string]interface{}) 428 ebs := &ec2.EBSBlockDevice{ 429 DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(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 := false 623 for _, v := range v.(*schema.Set).List() { 624 if strings.HasPrefix(v.(string), "sg-") { 625 match = true 626 break 627 } 628 } 629 630 useID = match 631 } 632 633 // Build up the security groups 634 sgs := make([]string, 0, len(instance.SecurityGroups)) 635 if useID { 636 for _, sg := range instance.SecurityGroups { 637 sgs = append(sgs, *sg.GroupID) 638 } 639 log.Printf("[DEBUG] Setting Security Group IDs: %#v", sgs) 640 if err := d.Set("vpc_security_group_ids", sgs); err != nil { 641 return err 642 } 643 } else { 644 for _, sg := range instance.SecurityGroups { 645 sgs = append(sgs, *sg.GroupName) 646 } 647 log.Printf("[DEBUG] Setting Security Group Names: %#v", sgs) 648 if err := d.Set("security_groups", sgs); err != nil { 649 return err 650 } 651 } 652 653 if err := readBlockDevices(d, instance, conn); err != nil { 654 return err 655 } 656 657 return nil 658 } 659 660 func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error { 661 conn := meta.(*AWSClient).ec2conn 662 663 d.Partial(true) 664 665 // SourceDestCheck can only be set on VPC instances 666 if d.Get("subnet_id").(string) != "" { 667 log.Printf("[INFO] Modifying instance %s", d.Id()) 668 _, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{ 669 InstanceID: aws.String(d.Id()), 670 SourceDestCheck: &ec2.AttributeBooleanValue{ 671 Value: aws.Boolean(d.Get("source_dest_check").(bool)), 672 }, 673 }) 674 if err != nil { 675 return err 676 } 677 } 678 679 // TODO(mitchellh): wait for the attributes we modified to 680 // persist the change... 681 682 if err := setTagsSDK(conn, d); err != nil { 683 return err 684 } else { 685 d.SetPartial("tags") 686 } 687 d.Partial(false) 688 689 return resourceAwsInstanceRead(d, meta) 690 } 691 692 func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error { 693 conn := meta.(*AWSClient).ec2conn 694 695 log.Printf("[INFO] Terminating instance: %s", d.Id()) 696 req := &ec2.TerminateInstancesInput{ 697 InstanceIDs: []*string{aws.String(d.Id())}, 698 } 699 if _, err := conn.TerminateInstances(req); err != nil { 700 return fmt.Errorf("Error terminating instance: %s", err) 701 } 702 703 log.Printf( 704 "[DEBUG] Waiting for instance (%s) to become terminated", 705 d.Id()) 706 707 stateConf := &resource.StateChangeConf{ 708 Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"}, 709 Target: "terminated", 710 Refresh: InstanceStateRefreshFunc(conn, d.Id()), 711 Timeout: 10 * time.Minute, 712 Delay: 10 * time.Second, 713 MinTimeout: 3 * time.Second, 714 } 715 716 _, err := stateConf.WaitForState() 717 if err != nil { 718 return fmt.Errorf( 719 "Error waiting for instance (%s) to terminate: %s", 720 d.Id(), err) 721 } 722 723 d.SetId("") 724 return nil 725 } 726 727 // InstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 728 // an EC2 instance. 729 func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc { 730 return func() (interface{}, string, error) { 731 resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{ 732 InstanceIDs: []*string{aws.String(instanceID)}, 733 }) 734 if err != nil { 735 if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidInstanceID.NotFound" { 736 // Set this to nil as if we didn't find anything. 737 resp = nil 738 } else { 739 log.Printf("Error on InstanceStateRefresh: %s", err) 740 return nil, "", err 741 } 742 } 743 744 if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 { 745 // Sometimes AWS just has consistency issues and doesn't see 746 // our instance yet. Return an empty state. 747 return nil, "", nil 748 } 749 750 i := resp.Reservations[0].Instances[0] 751 return i, *i.State.Name, nil 752 } 753 } 754 755 func readBlockDevices(d *schema.ResourceData, instance *ec2.Instance, conn *ec2.EC2) error { 756 ibds, err := readBlockDevicesFromInstance(instance, conn) 757 if err != nil { 758 return err 759 } 760 761 if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil { 762 return err 763 } 764 if ibds["root"] != nil { 765 if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil { 766 return err 767 } 768 } 769 770 return nil 771 } 772 773 func readBlockDevicesFromInstance(instance *ec2.Instance, conn *ec2.EC2) (map[string]interface{}, error) { 774 blockDevices := make(map[string]interface{}) 775 blockDevices["ebs"] = make([]map[string]interface{}, 0) 776 blockDevices["root"] = nil 777 778 instanceBlockDevices := make(map[string]*ec2.InstanceBlockDeviceMapping) 779 for _, bd := range instance.BlockDeviceMappings { 780 if bd.EBS != nil { 781 instanceBlockDevices[*(bd.EBS.VolumeID)] = bd 782 } 783 } 784 785 if len(instanceBlockDevices) == 0 { 786 return nil, nil 787 } 788 789 volIDs := make([]*string, 0, len(instanceBlockDevices)) 790 for volID := range instanceBlockDevices { 791 volIDs = append(volIDs, aws.String(volID)) 792 } 793 794 // Need to call DescribeVolumes to get volume_size and volume_type for each 795 // EBS block device 796 volResp, err := conn.DescribeVolumes(&ec2.DescribeVolumesInput{ 797 VolumeIDs: volIDs, 798 }) 799 if err != nil { 800 return nil, err 801 } 802 803 for _, vol := range volResp.Volumes { 804 instanceBd := instanceBlockDevices[*vol.VolumeID] 805 bd := make(map[string]interface{}) 806 807 if instanceBd.EBS != nil && instanceBd.EBS.DeleteOnTermination != nil { 808 bd["delete_on_termination"] = *instanceBd.EBS.DeleteOnTermination 809 } 810 if vol.Size != nil { 811 bd["volume_size"] = *vol.Size 812 } 813 if vol.VolumeType != nil { 814 bd["volume_type"] = *vol.VolumeType 815 } 816 if vol.IOPS != nil { 817 bd["iops"] = *vol.IOPS 818 } 819 820 if blockDeviceIsRoot(instanceBd, instance) { 821 blockDevices["root"] = bd 822 } else { 823 if instanceBd.DeviceName != nil { 824 bd["device_name"] = *instanceBd.DeviceName 825 } 826 if vol.Encrypted != nil { 827 bd["encrypted"] = *vol.Encrypted 828 } 829 if vol.SnapshotID != nil { 830 bd["snapshot_id"] = *vol.SnapshotID 831 } 832 833 blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd) 834 } 835 } 836 837 return blockDevices, nil 838 } 839 840 func blockDeviceIsRoot(bd *ec2.InstanceBlockDeviceMapping, instance *ec2.Instance) bool { 841 return (bd.DeviceName != nil && 842 instance.RootDeviceName != nil && 843 *bd.DeviceName == *instance.RootDeviceName) 844 } 845 846 func fetchRootDeviceName(ami string, conn *ec2.EC2) (*string, error) { 847 if ami == "" { 848 return nil, fmt.Errorf("Cannot fetch root device name for blank AMI ID.") 849 } 850 851 log.Printf("[DEBUG] Describing AMI %q to get root block device name", ami) 852 req := &ec2.DescribeImagesInput{ImageIDs: []*string{aws.String(ami)}} 853 if res, err := conn.DescribeImages(req); err == nil { 854 if len(res.Images) == 1 { 855 return res.Images[0].RootDeviceName, nil 856 } else { 857 return nil, fmt.Errorf("Expected 1 AMI for ID: %s, got: %#v", ami, res.Images) 858 } 859 } else { 860 return nil, err 861 } 862 }