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