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