github.com/ndarilek/terraform@v0.3.8-0.20150320140257-d3135c1b2bac/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 "device_name": &schema.Schema{ 269 Type: schema.TypeString, 270 Optional: true, 271 ForceNew: true, 272 Default: "/dev/sda1", 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 var buf bytes.Buffer 299 m := v.(map[string]interface{}) 300 buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool))) 301 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 302 // See the NOTE in "ebs_block_device" for why we skip iops here. 303 // buf.WriteString(fmt.Sprintf("%d-", m["iops"].(int))) 304 buf.WriteString(fmt.Sprintf("%d-", m["volume_size"].(int))) 305 buf.WriteString(fmt.Sprintf("%s-", m["volume_type"].(string))) 306 return hashcode.String(buf.String()) 307 }, 308 }, 309 }, 310 } 311 } 312 313 func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error { 314 ec2conn := meta.(*AWSClient).ec2conn 315 316 // Figure out user data 317 userData := "" 318 if v := d.Get("user_data"); v != nil { 319 userData = base64.StdEncoding.EncodeToString([]byte(v.(string))) 320 } 321 322 // check for non-default Subnet, and cast it to a String 323 var hasSubnet bool 324 subnet, hasSubnet := d.GetOk("subnet_id") 325 subnetID := subnet.(string) 326 327 placement := &ec2.Placement{ 328 AvailabilityZone: aws.String(d.Get("availability_zone").(string)), 329 } 330 331 if hasSubnet { 332 // Tenancy is only valid inside a VPC 333 // See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_Placement.html 334 if v := d.Get("tenancy").(string); v != "" { 335 placement.Tenancy = aws.String(v) 336 } 337 } 338 339 iam := &ec2.IAMInstanceProfileSpecification{ 340 Name: aws.String(d.Get("iam_instance_profile").(string)), 341 } 342 343 // Build the creation struct 344 runOpts := &ec2.RunInstancesRequest{ 345 ImageID: aws.String(d.Get("ami").(string)), 346 Placement: placement, 347 InstanceType: aws.String(d.Get("instance_type").(string)), 348 MaxCount: aws.Integer(1), 349 MinCount: aws.Integer(1), 350 UserData: aws.String(userData), 351 EBSOptimized: aws.Boolean(d.Get("ebs_optimized").(bool)), 352 IAMInstanceProfile: iam, 353 } 354 355 associatePublicIPAddress := false 356 if v := d.Get("associate_public_ip_address"); v != nil { 357 associatePublicIPAddress = v.(bool) 358 } 359 360 var groups []string 361 if v := d.Get("security_groups"); v != nil { 362 // Security group names. 363 // For a nondefault VPC, you must use security group IDs instead. 364 // See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html 365 for _, v := range v.(*schema.Set).List() { 366 str := v.(string) 367 groups = append(groups, str) 368 } 369 } 370 371 if hasSubnet && associatePublicIPAddress { 372 // If we have a non-default VPC / Subnet specified, we can flag 373 // AssociatePublicIpAddress to get a Public IP assigned. By default these are not provided. 374 // You cannot specify both SubnetId and the NetworkInterface.0.* parameters though, otherwise 375 // you get: Network interfaces and an instance-level subnet ID may not be specified on the same request 376 // You also need to attach Security Groups to the NetworkInterface instead of the instance, 377 // to avoid: Network interfaces and an instance-level security groups may not be specified on 378 // the same request 379 ni := ec2.InstanceNetworkInterfaceSpecification{ 380 AssociatePublicIPAddress: aws.Boolean(associatePublicIPAddress), 381 DeviceIndex: aws.Integer(0), 382 SubnetID: aws.String(subnetID), 383 } 384 385 if v, ok := d.GetOk("private_ip"); ok { 386 ni.PrivateIPAddress = aws.String(v.(string)) 387 } 388 389 if len(groups) > 0 { 390 ni.Groups = groups 391 } 392 393 runOpts.NetworkInterfaces = []ec2.InstanceNetworkInterfaceSpecification{ni} 394 } else { 395 if subnetID != "" { 396 runOpts.SubnetID = aws.String(subnetID) 397 } 398 399 if v, ok := d.GetOk("private_ip"); ok { 400 runOpts.PrivateIPAddress = aws.String(v.(string)) 401 } 402 if runOpts.SubnetID != nil && 403 *runOpts.SubnetID != "" { 404 runOpts.SecurityGroupIDs = groups 405 } else { 406 runOpts.SecurityGroups = groups 407 } 408 } 409 410 if v, ok := d.GetOk("key_name"); ok { 411 runOpts.KeyName = aws.String(v.(string)) 412 } 413 414 blockDevices := make([]ec2.BlockDeviceMapping, 0) 415 416 if v, ok := d.GetOk("ebs_block_device"); ok { 417 vL := v.(*schema.Set).List() 418 for _, v := range vL { 419 bd := v.(map[string]interface{}) 420 ebs := &ec2.EBSBlockDevice{ 421 DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)), 422 } 423 424 if v, ok := bd["snapshot_id"].(string); ok && v != "" { 425 ebs.SnapshotID = aws.String(v) 426 } 427 428 if v, ok := bd["volume_size"].(int); ok && v != 0 { 429 ebs.VolumeSize = aws.Integer(v) 430 } 431 432 if v, ok := bd["volume_type"].(string); ok && v != "" { 433 ebs.VolumeType = aws.String(v) 434 } 435 436 if v, ok := bd["iops"].(int); ok && v > 0 { 437 ebs.IOPS = aws.Integer(v) 438 } 439 440 blockDevices = append(blockDevices, ec2.BlockDeviceMapping{ 441 DeviceName: aws.String(bd["device_name"].(string)), 442 EBS: ebs, 443 }) 444 } 445 } 446 447 if v, ok := d.GetOk("ephemeral_block_device"); ok { 448 vL := v.(*schema.Set).List() 449 for _, v := range vL { 450 bd := v.(map[string]interface{}) 451 blockDevices = append(blockDevices, ec2.BlockDeviceMapping{ 452 DeviceName: aws.String(bd["device_name"].(string)), 453 VirtualName: aws.String(bd["virtual_name"].(string)), 454 }) 455 } 456 } 457 458 if v, ok := d.GetOk("root_block_device"); ok { 459 vL := v.(*schema.Set).List() 460 if len(vL) > 1 { 461 return fmt.Errorf("Cannot specify more than one root_block_device.") 462 } 463 for _, v := range vL { 464 bd := v.(map[string]interface{}) 465 ebs := &ec2.EBSBlockDevice{ 466 DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)), 467 } 468 469 if v, ok := bd["volume_size"].(int); ok && v != 0 { 470 ebs.VolumeSize = aws.Integer(v) 471 } 472 473 if v, ok := bd["volume_type"].(string); ok && v != "" { 474 ebs.VolumeType = aws.String(v) 475 } 476 477 if v, ok := bd["iops"].(int); ok && v > 0 { 478 ebs.IOPS = aws.Integer(v) 479 } 480 481 blockDevices = append(blockDevices, ec2.BlockDeviceMapping{ 482 DeviceName: aws.String(bd["device_name"].(string)), 483 EBS: ebs, 484 }) 485 } 486 } 487 488 if len(blockDevices) > 0 { 489 runOpts.BlockDeviceMappings = blockDevices 490 } 491 492 // Create the instance 493 log.Printf("[DEBUG] Run configuration: %#v", runOpts) 494 runResp, err := ec2conn.RunInstances(runOpts) 495 if err != nil { 496 return fmt.Errorf("Error launching source instance: %s", err) 497 } 498 499 instance := &runResp.Instances[0] 500 log.Printf("[INFO] Instance ID: %s", *instance.InstanceID) 501 502 // Store the resulting ID so we can look this up later 503 d.SetId(*instance.InstanceID) 504 505 // Wait for the instance to become running so we can get some attributes 506 // that aren't available until later. 507 log.Printf( 508 "[DEBUG] Waiting for instance (%s) to become running", 509 *instance.InstanceID) 510 511 stateConf := &resource.StateChangeConf{ 512 Pending: []string{"pending"}, 513 Target: "running", 514 Refresh: InstanceStateRefreshFunc(ec2conn, *instance.InstanceID), 515 Timeout: 10 * time.Minute, 516 Delay: 10 * time.Second, 517 MinTimeout: 3 * time.Second, 518 } 519 520 instanceRaw, err := stateConf.WaitForState() 521 if err != nil { 522 return fmt.Errorf( 523 "Error waiting for instance (%s) to become ready: %s", 524 *instance.InstanceID, err) 525 } 526 527 instance = instanceRaw.(*ec2.Instance) 528 529 // Initialize the connection info 530 if instance.PublicIPAddress != nil { 531 d.SetConnInfo(map[string]string{ 532 "type": "ssh", 533 "host": *instance.PublicIPAddress, 534 }) 535 } 536 537 // Set our attributes 538 if err := resourceAwsInstanceRead(d, meta); err != nil { 539 return err 540 } 541 542 // Update if we need to 543 return resourceAwsInstanceUpdate(d, meta) 544 } 545 546 func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { 547 ec2conn := meta.(*AWSClient).ec2conn 548 549 resp, err := ec2conn.DescribeInstances(&ec2.DescribeInstancesRequest{ 550 InstanceIDs: []string{d.Id()}, 551 }) 552 if err != nil { 553 // If the instance was not found, return nil so that we can show 554 // that the instance is gone. 555 if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidInstanceID.NotFound" { 556 d.SetId("") 557 return nil 558 } 559 560 // Some other error, report it 561 return err 562 } 563 564 // If nothing was found, then return no state 565 if len(resp.Reservations) == 0 { 566 d.SetId("") 567 return nil 568 } 569 570 instance := &resp.Reservations[0].Instances[0] 571 572 // If the instance is terminated, then it is gone 573 if *instance.State.Name == "terminated" { 574 d.SetId("") 575 return nil 576 } 577 578 if instance.Placement != nil { 579 d.Set("availability_zone", instance.Placement.AvailabilityZone) 580 } 581 if instance.Placement.Tenancy != nil { 582 d.Set("tenancy", instance.Placement.Tenancy) 583 } 584 585 d.Set("key_name", instance.KeyName) 586 d.Set("public_dns", instance.PublicDNSName) 587 d.Set("public_ip", instance.PublicIPAddress) 588 d.Set("private_dns", instance.PrivateDNSName) 589 d.Set("private_ip", instance.PrivateIPAddress) 590 if len(instance.NetworkInterfaces) > 0 { 591 d.Set("subnet_id", instance.NetworkInterfaces[0].SubnetID) 592 } else { 593 d.Set("subnet_id", instance.SubnetID) 594 } 595 d.Set("ebs_optimized", instance.EBSOptimized) 596 d.Set("tags", tagsToMap(instance.Tags)) 597 598 // Determine whether we're referring to security groups with 599 // IDs or names. We use a heuristic to figure this out. By default, 600 // we use IDs if we're in a VPC. However, if we previously had an 601 // all-name list of security groups, we use names. Or, if we had any 602 // IDs, we use IDs. 603 useID := instance.SubnetID != nil && *instance.SubnetID != "" 604 if v := d.Get("security_groups"); v != nil { 605 match := false 606 for _, v := range v.(*schema.Set).List() { 607 if strings.HasPrefix(v.(string), "sg-") { 608 match = true 609 break 610 } 611 } 612 613 useID = match 614 } 615 616 // Build up the security groups 617 sgs := make([]string, len(instance.SecurityGroups)) 618 for i, sg := range instance.SecurityGroups { 619 if useID { 620 sgs[i] = *sg.GroupID 621 } else { 622 sgs[i] = *sg.GroupName 623 } 624 } 625 d.Set("security_groups", sgs) 626 627 if err := readBlockDevices(d, instance, ec2conn); err != nil { 628 return err 629 } 630 631 return nil 632 } 633 634 func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error { 635 ec2conn := meta.(*AWSClient).ec2conn 636 637 // SourceDestCheck can only be set on VPC instances 638 if d.Get("subnet_id").(string) != "" { 639 log.Printf("[INFO] Modifying instance %s", d.Id()) 640 err := ec2conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeRequest{ 641 InstanceID: aws.String(d.Id()), 642 SourceDestCheck: &ec2.AttributeBooleanValue{ 643 Value: aws.Boolean(d.Get("source_dest_check").(bool)), 644 }, 645 }) 646 if err != nil { 647 return err 648 } 649 } 650 651 // TODO(mitchellh): wait for the attributes we modified to 652 // persist the change... 653 654 if err := setTags(ec2conn, d); err != nil { 655 return err 656 } else { 657 d.SetPartial("tags") 658 } 659 660 return nil 661 } 662 663 func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error { 664 ec2conn := meta.(*AWSClient).ec2conn 665 666 log.Printf("[INFO] Terminating instance: %s", d.Id()) 667 req := &ec2.TerminateInstancesRequest{ 668 InstanceIDs: []string{d.Id()}, 669 } 670 if _, err := ec2conn.TerminateInstances(req); err != nil { 671 return fmt.Errorf("Error terminating instance: %s", err) 672 } 673 674 log.Printf( 675 "[DEBUG] Waiting for instance (%s) to become terminated", 676 d.Id()) 677 678 stateConf := &resource.StateChangeConf{ 679 Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"}, 680 Target: "terminated", 681 Refresh: InstanceStateRefreshFunc(ec2conn, d.Id()), 682 Timeout: 10 * time.Minute, 683 Delay: 10 * time.Second, 684 MinTimeout: 3 * time.Second, 685 } 686 687 _, err := stateConf.WaitForState() 688 if err != nil { 689 return fmt.Errorf( 690 "Error waiting for instance (%s) to terminate: %s", 691 d.Id(), err) 692 } 693 694 d.SetId("") 695 return nil 696 } 697 698 // InstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 699 // an EC2 instance. 700 func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc { 701 return func() (interface{}, string, error) { 702 resp, err := conn.DescribeInstances(&ec2.DescribeInstancesRequest{ 703 InstanceIDs: []string{instanceID}, 704 }) 705 if err != nil { 706 if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidInstanceID.NotFound" { 707 // Set this to nil as if we didn't find anything. 708 resp = nil 709 } else { 710 log.Printf("Error on InstanceStateRefresh: %s", err) 711 return nil, "", err 712 } 713 } 714 715 if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 { 716 // Sometimes AWS just has consistency issues and doesn't see 717 // our instance yet. Return an empty state. 718 return nil, "", nil 719 } 720 721 i := &resp.Reservations[0].Instances[0] 722 return i, *i.State.Name, nil 723 } 724 } 725 726 func readBlockDevices(d *schema.ResourceData, instance *ec2.Instance, ec2conn *ec2.EC2) error { 727 ibds, err := readBlockDevicesFromInstance(instance, ec2conn) 728 if err != nil { 729 return err 730 } 731 732 if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil { 733 return err 734 } 735 if ibds["root"] != nil { 736 if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil { 737 return err 738 } 739 } 740 741 return nil 742 } 743 744 func readBlockDevicesFromInstance(instance *ec2.Instance, ec2conn *ec2.EC2) (map[string]interface{}, error) { 745 blockDevices := make(map[string]interface{}) 746 blockDevices["ebs"] = make([]map[string]interface{}, 0) 747 blockDevices["root"] = nil 748 749 instanceBlockDevices := make(map[string]ec2.InstanceBlockDeviceMapping) 750 for _, bd := range instance.BlockDeviceMappings { 751 if bd.EBS != nil { 752 instanceBlockDevices[*(bd.EBS.VolumeID)] = bd 753 } 754 } 755 756 if len(instanceBlockDevices) == 0 { 757 return nil, nil 758 } 759 760 volIDs := make([]string, 0, len(instanceBlockDevices)) 761 for volID := range instanceBlockDevices { 762 volIDs = append(volIDs, volID) 763 } 764 765 // Need to call DescribeVolumes to get volume_size and volume_type for each 766 // EBS block device 767 volResp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesRequest{ 768 VolumeIDs: volIDs, 769 }) 770 if err != nil { 771 return nil, err 772 } 773 774 for _, vol := range volResp.Volumes { 775 instanceBd := instanceBlockDevices[*vol.VolumeID] 776 bd := make(map[string]interface{}) 777 778 if instanceBd.EBS != nil && instanceBd.EBS.DeleteOnTermination != nil { 779 bd["delete_on_termination"] = *instanceBd.EBS.DeleteOnTermination 780 } 781 if instanceBd.DeviceName != nil { 782 bd["device_name"] = *instanceBd.DeviceName 783 } 784 if vol.Size != nil { 785 bd["volume_size"] = *vol.Size 786 } 787 if vol.VolumeType != nil { 788 bd["volume_type"] = *vol.VolumeType 789 } 790 if vol.IOPS != nil { 791 bd["iops"] = *vol.IOPS 792 } 793 794 if blockDeviceIsRoot(instanceBd, instance) { 795 blockDevices["root"] = bd 796 } else { 797 if vol.Encrypted != nil { 798 bd["encrypted"] = *vol.Encrypted 799 } 800 if vol.SnapshotID != nil { 801 bd["snapshot_id"] = *vol.SnapshotID 802 } 803 804 blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd) 805 } 806 } 807 808 return blockDevices, nil 809 } 810 811 func blockDeviceIsRoot(bd ec2.InstanceBlockDeviceMapping, instance *ec2.Instance) bool { 812 return (bd.DeviceName != nil && 813 instance.RootDeviceName != nil && 814 *bd.DeviceName == *instance.RootDeviceName) 815 }