github.com/econnell/terraform@v0.5.4-0.20150722160631-78eb236786a4/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/aws/aws-sdk-go/aws" 14 "github.com/aws/aws-sdk-go/aws/awserr" 15 "github.com/aws/aws-sdk-go/aws/awsutil" 16 "github.com/aws/aws-sdk-go/service/ec2" 17 "github.com/hashicorp/terraform/helper/hashcode" 18 "github.com/hashicorp/terraform/helper/resource" 19 "github.com/hashicorp/terraform/helper/schema" 20 ) 21 22 func resourceAwsInstance() *schema.Resource { 23 return &schema.Resource{ 24 Create: resourceAwsInstanceCreate, 25 Read: resourceAwsInstanceRead, 26 Update: resourceAwsInstanceUpdate, 27 Delete: resourceAwsInstanceDelete, 28 29 SchemaVersion: 1, 30 MigrateState: resourceAwsInstanceMigrateState, 31 32 Schema: map[string]*schema.Schema{ 33 "ami": &schema.Schema{ 34 Type: schema.TypeString, 35 Required: true, 36 ForceNew: true, 37 }, 38 39 "associate_public_ip_address": &schema.Schema{ 40 Type: schema.TypeBool, 41 ForceNew: true, 42 Optional: true, 43 }, 44 45 "availability_zone": &schema.Schema{ 46 Type: schema.TypeString, 47 Optional: true, 48 Computed: true, 49 ForceNew: true, 50 }, 51 52 "placement_group": &schema.Schema{ 53 Type: schema.TypeString, 54 Optional: true, 55 Computed: true, 56 ForceNew: true, 57 }, 58 59 "instance_type": &schema.Schema{ 60 Type: schema.TypeString, 61 Required: true, 62 ForceNew: true, 63 }, 64 65 "key_name": &schema.Schema{ 66 Type: schema.TypeString, 67 Optional: true, 68 ForceNew: true, 69 Computed: true, 70 }, 71 72 "subnet_id": &schema.Schema{ 73 Type: schema.TypeString, 74 Optional: true, 75 Computed: true, 76 ForceNew: true, 77 }, 78 79 "private_ip": &schema.Schema{ 80 Type: schema.TypeString, 81 Optional: true, 82 ForceNew: true, 83 Computed: true, 84 }, 85 86 "source_dest_check": &schema.Schema{ 87 Type: schema.TypeBool, 88 Optional: true, 89 Default: true, 90 }, 91 92 "user_data": &schema.Schema{ 93 Type: schema.TypeString, 94 Optional: true, 95 ForceNew: true, 96 StateFunc: func(v interface{}) string { 97 switch v.(type) { 98 case string: 99 hash := sha1.Sum([]byte(v.(string))) 100 return hex.EncodeToString(hash[:]) 101 default: 102 return "" 103 } 104 }, 105 }, 106 107 "security_groups": &schema.Schema{ 108 Type: schema.TypeSet, 109 Optional: true, 110 Computed: true, 111 ForceNew: true, 112 Elem: &schema.Schema{Type: schema.TypeString}, 113 Set: schema.HashString, 114 }, 115 116 "vpc_security_group_ids": &schema.Schema{ 117 Type: schema.TypeSet, 118 Optional: true, 119 Computed: true, 120 Elem: &schema.Schema{Type: schema.TypeString}, 121 Set: func(v interface{}) int { 122 return hashcode.String(v.(string)) 123 }, 124 }, 125 126 "public_dns": &schema.Schema{ 127 Type: schema.TypeString, 128 Computed: true, 129 }, 130 131 "public_ip": &schema.Schema{ 132 Type: schema.TypeString, 133 Computed: true, 134 }, 135 136 "private_dns": &schema.Schema{ 137 Type: schema.TypeString, 138 Computed: true, 139 }, 140 141 "ebs_optimized": &schema.Schema{ 142 Type: schema.TypeBool, 143 Optional: true, 144 }, 145 146 "disable_api_termination": &schema.Schema{ 147 Type: schema.TypeBool, 148 Optional: true, 149 }, 150 151 "monitoring": &schema.Schema{ 152 Type: schema.TypeBool, 153 Optional: true, 154 }, 155 156 "iam_instance_profile": &schema.Schema{ 157 Type: schema.TypeString, 158 ForceNew: true, 159 Optional: true, 160 }, 161 162 "tenancy": &schema.Schema{ 163 Type: schema.TypeString, 164 Optional: true, 165 Computed: true, 166 ForceNew: true, 167 }, 168 169 "tags": tagsSchema(), 170 171 "block_device": &schema.Schema{ 172 Type: schema.TypeMap, 173 Optional: true, 174 Removed: "Split out into three sub-types; see Changelog and Docs", 175 }, 176 177 "ebs_block_device": &schema.Schema{ 178 Type: schema.TypeSet, 179 Optional: true, 180 Computed: true, 181 Elem: &schema.Resource{ 182 Schema: map[string]*schema.Schema{ 183 "delete_on_termination": &schema.Schema{ 184 Type: schema.TypeBool, 185 Optional: true, 186 Default: true, 187 ForceNew: true, 188 }, 189 190 "device_name": &schema.Schema{ 191 Type: schema.TypeString, 192 Required: true, 193 ForceNew: true, 194 }, 195 196 "encrypted": &schema.Schema{ 197 Type: schema.TypeBool, 198 Optional: true, 199 Computed: true, 200 ForceNew: true, 201 }, 202 203 "iops": &schema.Schema{ 204 Type: schema.TypeInt, 205 Optional: true, 206 Computed: true, 207 ForceNew: true, 208 }, 209 210 "snapshot_id": &schema.Schema{ 211 Type: schema.TypeString, 212 Optional: true, 213 Computed: true, 214 ForceNew: true, 215 }, 216 217 "volume_size": &schema.Schema{ 218 Type: schema.TypeInt, 219 Optional: true, 220 Computed: true, 221 ForceNew: true, 222 }, 223 224 "volume_type": &schema.Schema{ 225 Type: schema.TypeString, 226 Optional: true, 227 Computed: true, 228 ForceNew: true, 229 }, 230 }, 231 }, 232 Set: func(v interface{}) int { 233 var buf bytes.Buffer 234 m := v.(map[string]interface{}) 235 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 236 buf.WriteString(fmt.Sprintf("%s-", m["snapshot_id"].(string))) 237 return hashcode.String(buf.String()) 238 }, 239 }, 240 241 "ephemeral_block_device": &schema.Schema{ 242 Type: schema.TypeSet, 243 Optional: true, 244 Computed: true, 245 ForceNew: true, 246 Elem: &schema.Resource{ 247 Schema: map[string]*schema.Schema{ 248 "device_name": &schema.Schema{ 249 Type: schema.TypeString, 250 Required: true, 251 }, 252 253 "virtual_name": &schema.Schema{ 254 Type: schema.TypeString, 255 Required: true, 256 }, 257 }, 258 }, 259 Set: func(v interface{}) int { 260 var buf bytes.Buffer 261 m := v.(map[string]interface{}) 262 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 263 buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string))) 264 return hashcode.String(buf.String()) 265 }, 266 }, 267 268 "root_block_device": &schema.Schema{ 269 // TODO: This is a set because we don't support singleton 270 // sub-resources today. We'll enforce that the set only ever has 271 // length zero or one below. When TF gains support for 272 // sub-resources this can be converted. 273 Type: schema.TypeSet, 274 Optional: true, 275 Computed: true, 276 Elem: &schema.Resource{ 277 // "You can only modify the volume size, volume type, and Delete on 278 // Termination flag on the block device mapping entry for the root 279 // device volume." - bit.ly/ec2bdmap 280 Schema: map[string]*schema.Schema{ 281 "delete_on_termination": &schema.Schema{ 282 Type: schema.TypeBool, 283 Optional: true, 284 Default: true, 285 ForceNew: true, 286 }, 287 288 "iops": &schema.Schema{ 289 Type: schema.TypeInt, 290 Optional: true, 291 Computed: true, 292 ForceNew: true, 293 }, 294 295 "volume_size": &schema.Schema{ 296 Type: schema.TypeInt, 297 Optional: true, 298 Computed: true, 299 ForceNew: true, 300 }, 301 302 "volume_type": &schema.Schema{ 303 Type: schema.TypeString, 304 Optional: true, 305 Computed: true, 306 ForceNew: true, 307 }, 308 }, 309 }, 310 Set: func(v interface{}) int { 311 // there can be only one root device; no need to hash anything 312 return 0 313 }, 314 }, 315 }, 316 } 317 } 318 319 func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error { 320 conn := meta.(*AWSClient).ec2conn 321 322 instanceOpts, err := buildAwsInstanceOpts(d, meta) 323 if err != nil { 324 return err 325 } 326 327 // Build the creation struct 328 runOpts := &ec2.RunInstancesInput{ 329 BlockDeviceMappings: instanceOpts.BlockDeviceMappings, 330 DisableAPITermination: instanceOpts.DisableAPITermination, 331 EBSOptimized: instanceOpts.EBSOptimized, 332 Monitoring: instanceOpts.Monitoring, 333 IAMInstanceProfile: instanceOpts.IAMInstanceProfile, 334 ImageID: instanceOpts.ImageID, 335 InstanceType: instanceOpts.InstanceType, 336 KeyName: instanceOpts.KeyName, 337 MaxCount: aws.Long(int64(1)), 338 MinCount: aws.Long(int64(1)), 339 NetworkInterfaces: instanceOpts.NetworkInterfaces, 340 Placement: instanceOpts.Placement, 341 PrivateIPAddress: instanceOpts.PrivateIPAddress, 342 SecurityGroupIDs: instanceOpts.SecurityGroupIDs, 343 SecurityGroups: instanceOpts.SecurityGroups, 344 SubnetID: instanceOpts.SubnetID, 345 UserData: instanceOpts.UserData64, 346 } 347 348 // Create the instance 349 log.Printf("[DEBUG] Run configuration: %s", awsutil.StringValue(runOpts)) 350 351 var runResp *ec2.Reservation 352 for i := 0; i < 5; i++ { 353 runResp, err = conn.RunInstances(runOpts) 354 if awsErr, ok := err.(awserr.Error); ok { 355 // IAM profiles can take ~10 seconds to propagate in AWS: 356 // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console 357 if awsErr.Code() == "InvalidParameterValue" && strings.Contains(awsErr.Message(), "Invalid IAM Instance Profile") { 358 log.Printf("[DEBUG] Invalid IAM Instance Profile referenced, retrying...") 359 time.Sleep(2 * time.Second) 360 continue 361 } 362 } 363 break 364 } 365 if err != nil { 366 return fmt.Errorf("Error launching source instance: %s", err) 367 } 368 369 instance := runResp.Instances[0] 370 log.Printf("[INFO] Instance ID: %s", *instance.InstanceID) 371 372 // Store the resulting ID so we can look this up later 373 d.SetId(*instance.InstanceID) 374 375 // Wait for the instance to become running so we can get some attributes 376 // that aren't available until later. 377 log.Printf( 378 "[DEBUG] Waiting for instance (%s) to become running", 379 *instance.InstanceID) 380 381 stateConf := &resource.StateChangeConf{ 382 Pending: []string{"pending"}, 383 Target: "running", 384 Refresh: InstanceStateRefreshFunc(conn, *instance.InstanceID), 385 Timeout: 10 * time.Minute, 386 Delay: 10 * time.Second, 387 MinTimeout: 3 * time.Second, 388 } 389 390 instanceRaw, err := stateConf.WaitForState() 391 if err != nil { 392 return fmt.Errorf( 393 "Error waiting for instance (%s) to become ready: %s", 394 *instance.InstanceID, err) 395 } 396 397 instance = instanceRaw.(*ec2.Instance) 398 399 // Initialize the connection info 400 if instance.PublicIPAddress != nil { 401 d.SetConnInfo(map[string]string{ 402 "type": "ssh", 403 "host": *instance.PublicIPAddress, 404 }) 405 } else if instance.PrivateIPAddress != nil { 406 d.SetConnInfo(map[string]string{ 407 "type": "ssh", 408 "host": *instance.PrivateIPAddress, 409 }) 410 } 411 412 // Set our attributes 413 if err := resourceAwsInstanceRead(d, meta); err != nil { 414 return err 415 } 416 417 // Update if we need to 418 return resourceAwsInstanceUpdate(d, meta) 419 } 420 421 func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { 422 conn := meta.(*AWSClient).ec2conn 423 424 resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{ 425 InstanceIDs: []*string{aws.String(d.Id())}, 426 }) 427 if err != nil { 428 // If the instance was not found, return nil so that we can show 429 // that the instance is gone. 430 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidInstanceID.NotFound" { 431 d.SetId("") 432 return nil 433 } 434 435 // Some other error, report it 436 return err 437 } 438 439 // If nothing was found, then return no state 440 if len(resp.Reservations) == 0 { 441 d.SetId("") 442 return nil 443 } 444 445 instance := resp.Reservations[0].Instances[0] 446 447 // If the instance is terminated, then it is gone 448 if *instance.State.Name == "terminated" { 449 d.SetId("") 450 return nil 451 } 452 453 if instance.Placement != nil { 454 d.Set("availability_zone", instance.Placement.AvailabilityZone) 455 } 456 if instance.Placement.Tenancy != nil { 457 d.Set("tenancy", instance.Placement.Tenancy) 458 } 459 460 d.Set("ami", instance.ImageID) 461 d.Set("instance_type", instance.InstanceType) 462 d.Set("key_name", instance.KeyName) 463 d.Set("public_dns", instance.PublicDNSName) 464 d.Set("public_ip", instance.PublicIPAddress) 465 d.Set("private_dns", instance.PrivateDNSName) 466 d.Set("private_ip", instance.PrivateIPAddress) 467 if len(instance.NetworkInterfaces) > 0 { 468 d.Set("subnet_id", instance.NetworkInterfaces[0].SubnetID) 469 } else { 470 d.Set("subnet_id", instance.SubnetID) 471 } 472 d.Set("ebs_optimized", instance.EBSOptimized) 473 474 if instance.Monitoring != nil && instance.Monitoring.State != nil { 475 monitoringState := *instance.Monitoring.State 476 d.Set("monitoring", monitoringState == "enabled" || monitoringState == "pending") 477 } 478 479 d.Set("tags", tagsToMap(instance.Tags)) 480 481 // Determine whether we're referring to security groups with 482 // IDs or names. We use a heuristic to figure this out. By default, 483 // we use IDs if we're in a VPC. However, if we previously had an 484 // all-name list of security groups, we use names. Or, if we had any 485 // IDs, we use IDs. 486 useID := instance.SubnetID != nil && *instance.SubnetID != "" 487 if v := d.Get("security_groups"); v != nil { 488 match := useID 489 sgs := v.(*schema.Set).List() 490 if len(sgs) > 0 { 491 match = false 492 for _, v := range v.(*schema.Set).List() { 493 if strings.HasPrefix(v.(string), "sg-") { 494 match = true 495 break 496 } 497 } 498 } 499 500 useID = match 501 } 502 503 // Build up the security groups 504 sgs := make([]string, 0, len(instance.SecurityGroups)) 505 if useID { 506 for _, sg := range instance.SecurityGroups { 507 sgs = append(sgs, *sg.GroupID) 508 } 509 log.Printf("[DEBUG] Setting Security Group IDs: %#v", sgs) 510 if err := d.Set("vpc_security_group_ids", sgs); err != nil { 511 return err 512 } 513 } else { 514 for _, sg := range instance.SecurityGroups { 515 sgs = append(sgs, *sg.GroupName) 516 } 517 log.Printf("[DEBUG] Setting Security Group Names: %#v", sgs) 518 if err := d.Set("security_groups", sgs); err != nil { 519 return err 520 } 521 } 522 523 if err := readBlockDevices(d, instance, conn); err != nil { 524 return err 525 } 526 527 return nil 528 } 529 530 func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error { 531 conn := meta.(*AWSClient).ec2conn 532 533 d.Partial(true) 534 if err := setTags(conn, d); err != nil { 535 return err 536 } else { 537 d.SetPartial("tags") 538 } 539 540 // SourceDestCheck can only be set on VPC instances 541 if d.Get("subnet_id").(string) != "" { 542 log.Printf("[INFO] Modifying instance %s", d.Id()) 543 _, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{ 544 InstanceID: aws.String(d.Id()), 545 SourceDestCheck: &ec2.AttributeBooleanValue{ 546 Value: aws.Boolean(d.Get("source_dest_check").(bool)), 547 }, 548 }) 549 if err != nil { 550 return err 551 } 552 } 553 554 if d.HasChange("vpc_security_group_ids") { 555 var groups []*string 556 if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 { 557 for _, v := range v.List() { 558 groups = append(groups, aws.String(v.(string))) 559 } 560 } 561 _, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{ 562 InstanceID: aws.String(d.Id()), 563 Groups: groups, 564 }) 565 if err != nil { 566 return err 567 } 568 } 569 570 if d.HasChange("disable_api_termination") { 571 _, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{ 572 InstanceID: aws.String(d.Id()), 573 DisableAPITermination: &ec2.AttributeBooleanValue{ 574 Value: aws.Boolean(d.Get("disable_api_termination").(bool)), 575 }, 576 }) 577 if err != nil { 578 return err 579 } 580 } 581 582 if d.HasChange("monitoring") { 583 var mErr error 584 if d.Get("monitoring").(bool) { 585 log.Printf("[DEBUG] Enabling monitoring for Instance (%s)", d.Id()) 586 _, mErr = conn.MonitorInstances(&ec2.MonitorInstancesInput{ 587 InstanceIDs: []*string{aws.String(d.Id())}, 588 }) 589 } else { 590 log.Printf("[DEBUG] Disabling monitoring for Instance (%s)", d.Id()) 591 _, mErr = conn.UnmonitorInstances(&ec2.UnmonitorInstancesInput{ 592 InstanceIDs: []*string{aws.String(d.Id())}, 593 }) 594 } 595 if mErr != nil { 596 return fmt.Errorf("[WARN] Error updating Instance monitoring: %s", mErr) 597 } 598 } 599 600 // TODO(mitchellh): wait for the attributes we modified to 601 // persist the change... 602 603 d.Partial(false) 604 605 return resourceAwsInstanceRead(d, meta) 606 } 607 608 func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error { 609 conn := meta.(*AWSClient).ec2conn 610 611 if err := awsTerminateInstance(conn, d.Id()); err != nil { 612 return err 613 } 614 615 d.SetId("") 616 return nil 617 } 618 619 // InstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 620 // an EC2 instance. 621 func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc { 622 return func() (interface{}, string, error) { 623 resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{ 624 InstanceIDs: []*string{aws.String(instanceID)}, 625 }) 626 if err != nil { 627 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidInstanceID.NotFound" { 628 // Set this to nil as if we didn't find anything. 629 resp = nil 630 } else { 631 log.Printf("Error on InstanceStateRefresh: %s", err) 632 return nil, "", err 633 } 634 } 635 636 if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 { 637 // Sometimes AWS just has consistency issues and doesn't see 638 // our instance yet. Return an empty state. 639 return nil, "", nil 640 } 641 642 i := resp.Reservations[0].Instances[0] 643 return i, *i.State.Name, nil 644 } 645 } 646 647 func readBlockDevices(d *schema.ResourceData, instance *ec2.Instance, conn *ec2.EC2) error { 648 ibds, err := readBlockDevicesFromInstance(instance, conn) 649 if err != nil { 650 return err 651 } 652 653 if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil { 654 return err 655 } 656 if ibds["root"] != nil { 657 if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil { 658 return err 659 } 660 } 661 662 return nil 663 } 664 665 func readBlockDevicesFromInstance(instance *ec2.Instance, conn *ec2.EC2) (map[string]interface{}, error) { 666 blockDevices := make(map[string]interface{}) 667 blockDevices["ebs"] = make([]map[string]interface{}, 0) 668 blockDevices["root"] = nil 669 670 instanceBlockDevices := make(map[string]*ec2.InstanceBlockDeviceMapping) 671 for _, bd := range instance.BlockDeviceMappings { 672 if bd.EBS != nil { 673 instanceBlockDevices[*(bd.EBS.VolumeID)] = bd 674 } 675 } 676 677 if len(instanceBlockDevices) == 0 { 678 return nil, nil 679 } 680 681 volIDs := make([]*string, 0, len(instanceBlockDevices)) 682 for volID := range instanceBlockDevices { 683 volIDs = append(volIDs, aws.String(volID)) 684 } 685 686 // Need to call DescribeVolumes to get volume_size and volume_type for each 687 // EBS block device 688 volResp, err := conn.DescribeVolumes(&ec2.DescribeVolumesInput{ 689 VolumeIDs: volIDs, 690 }) 691 if err != nil { 692 return nil, err 693 } 694 695 for _, vol := range volResp.Volumes { 696 instanceBd := instanceBlockDevices[*vol.VolumeID] 697 bd := make(map[string]interface{}) 698 699 if instanceBd.EBS != nil && instanceBd.EBS.DeleteOnTermination != nil { 700 bd["delete_on_termination"] = *instanceBd.EBS.DeleteOnTermination 701 } 702 if vol.Size != nil { 703 bd["volume_size"] = *vol.Size 704 } 705 if vol.VolumeType != nil { 706 bd["volume_type"] = *vol.VolumeType 707 } 708 if vol.IOPS != nil { 709 bd["iops"] = *vol.IOPS 710 } 711 712 if blockDeviceIsRoot(instanceBd, instance) { 713 blockDevices["root"] = bd 714 } else { 715 if instanceBd.DeviceName != nil { 716 bd["device_name"] = *instanceBd.DeviceName 717 } 718 if vol.Encrypted != nil { 719 bd["encrypted"] = *vol.Encrypted 720 } 721 if vol.SnapshotID != nil { 722 bd["snapshot_id"] = *vol.SnapshotID 723 } 724 725 blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd) 726 } 727 } 728 729 return blockDevices, nil 730 } 731 732 func blockDeviceIsRoot(bd *ec2.InstanceBlockDeviceMapping, instance *ec2.Instance) bool { 733 return (bd.DeviceName != nil && 734 instance.RootDeviceName != nil && 735 *bd.DeviceName == *instance.RootDeviceName) 736 } 737 738 func fetchRootDeviceName(ami string, conn *ec2.EC2) (*string, error) { 739 if ami == "" { 740 return nil, fmt.Errorf("Cannot fetch root device name for blank AMI ID.") 741 } 742 743 log.Printf("[DEBUG] Describing AMI %q to get root block device name", ami) 744 res, err := conn.DescribeImages(&ec2.DescribeImagesInput{ 745 ImageIDs: []*string{aws.String(ami)}, 746 }) 747 if err != nil { 748 return nil, err 749 } 750 751 // For a bad image, we just return nil so we don't block a refresh 752 if len(res.Images) == 0 { 753 return nil, nil 754 } 755 756 image := res.Images[0] 757 rootDeviceName := image.RootDeviceName 758 759 // Some AMIs have a RootDeviceName like "/dev/sda1" that does not appear as a 760 // DeviceName in the BlockDeviceMapping list (which will instead have 761 // something like "/dev/sda") 762 // 763 // While this seems like it breaks an invariant of AMIs, it ends up working 764 // on the AWS side, and AMIs like this are common enough that we need to 765 // special case it so Terraform does the right thing. 766 // 767 // Our heuristic is: if the RootDeviceName does not appear in the 768 // BlockDeviceMapping, assume that the DeviceName of the first 769 // BlockDeviceMapping entry serves as the root device. 770 rootDeviceNameInMapping := false 771 for _, bdm := range image.BlockDeviceMappings { 772 if bdm.DeviceName == image.RootDeviceName { 773 rootDeviceNameInMapping = true 774 } 775 } 776 777 if !rootDeviceNameInMapping && len(image.BlockDeviceMappings) > 0 { 778 rootDeviceName = image.BlockDeviceMappings[0].DeviceName 779 } 780 781 return rootDeviceName, nil 782 } 783 784 func readBlockDeviceMappingsFromConfig( 785 d *schema.ResourceData, conn *ec2.EC2) ([]*ec2.BlockDeviceMapping, error) { 786 blockDevices := make([]*ec2.BlockDeviceMapping, 0) 787 788 if v, ok := d.GetOk("ebs_block_device"); ok { 789 vL := v.(*schema.Set).List() 790 for _, v := range vL { 791 bd := v.(map[string]interface{}) 792 ebs := &ec2.EBSBlockDevice{ 793 DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)), 794 } 795 796 if v, ok := bd["snapshot_id"].(string); ok && v != "" { 797 ebs.SnapshotID = aws.String(v) 798 } 799 800 if v, ok := bd["encrypted"].(bool); ok && v { 801 ebs.Encrypted = aws.Boolean(v) 802 } 803 804 if v, ok := bd["volume_size"].(int); ok && v != 0 { 805 ebs.VolumeSize = aws.Long(int64(v)) 806 } 807 808 if v, ok := bd["volume_type"].(string); ok && v != "" { 809 ebs.VolumeType = aws.String(v) 810 } 811 812 if v, ok := bd["iops"].(int); ok && v > 0 { 813 ebs.IOPS = aws.Long(int64(v)) 814 } 815 816 blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{ 817 DeviceName: aws.String(bd["device_name"].(string)), 818 EBS: ebs, 819 }) 820 } 821 } 822 823 if v, ok := d.GetOk("ephemeral_block_device"); ok { 824 vL := v.(*schema.Set).List() 825 for _, v := range vL { 826 bd := v.(map[string]interface{}) 827 blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{ 828 DeviceName: aws.String(bd["device_name"].(string)), 829 VirtualName: aws.String(bd["virtual_name"].(string)), 830 }) 831 } 832 } 833 834 if v, ok := d.GetOk("root_block_device"); ok { 835 vL := v.(*schema.Set).List() 836 if len(vL) > 1 { 837 return nil, fmt.Errorf("Cannot specify more than one root_block_device.") 838 } 839 for _, v := range vL { 840 bd := v.(map[string]interface{}) 841 ebs := &ec2.EBSBlockDevice{ 842 DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)), 843 } 844 845 if v, ok := bd["volume_size"].(int); ok && v != 0 { 846 ebs.VolumeSize = aws.Long(int64(v)) 847 } 848 849 if v, ok := bd["volume_type"].(string); ok && v != "" { 850 ebs.VolumeType = aws.String(v) 851 } 852 853 if v, ok := bd["iops"].(int); ok && v > 0 { 854 ebs.IOPS = aws.Long(int64(v)) 855 } 856 857 if dn, err := fetchRootDeviceName(d.Get("ami").(string), conn); err == nil { 858 if dn == nil { 859 return nil, fmt.Errorf( 860 "Expected 1 AMI for ID: %s, got none", 861 d.Get("ami").(string)) 862 } 863 864 blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{ 865 DeviceName: dn, 866 EBS: ebs, 867 }) 868 } else { 869 return nil, err 870 } 871 } 872 } 873 874 return blockDevices, nil 875 } 876 877 type awsInstanceOpts struct { 878 BlockDeviceMappings []*ec2.BlockDeviceMapping 879 DisableAPITermination *bool 880 EBSOptimized *bool 881 Monitoring *ec2.RunInstancesMonitoringEnabled 882 IAMInstanceProfile *ec2.IAMInstanceProfileSpecification 883 ImageID *string 884 InstanceType *string 885 KeyName *string 886 NetworkInterfaces []*ec2.InstanceNetworkInterfaceSpecification 887 Placement *ec2.Placement 888 PrivateIPAddress *string 889 SecurityGroupIDs []*string 890 SecurityGroups []*string 891 SpotPlacement *ec2.SpotPlacement 892 SubnetID *string 893 UserData64 *string 894 } 895 896 func buildAwsInstanceOpts( 897 d *schema.ResourceData, meta interface{}) (*awsInstanceOpts, error) { 898 conn := meta.(*AWSClient).ec2conn 899 900 opts := &awsInstanceOpts{ 901 DisableAPITermination: aws.Boolean(d.Get("disable_api_termination").(bool)), 902 EBSOptimized: aws.Boolean(d.Get("ebs_optimized").(bool)), 903 ImageID: aws.String(d.Get("ami").(string)), 904 InstanceType: aws.String(d.Get("instance_type").(string)), 905 } 906 907 opts.Monitoring = &ec2.RunInstancesMonitoringEnabled{ 908 Enabled: aws.Boolean(d.Get("monitoring").(bool)), 909 } 910 911 opts.IAMInstanceProfile = &ec2.IAMInstanceProfileSpecification{ 912 Name: aws.String(d.Get("iam_instance_profile").(string)), 913 } 914 915 opts.UserData64 = aws.String( 916 base64.StdEncoding.EncodeToString([]byte(d.Get("user_data").(string)))) 917 918 // check for non-default Subnet, and cast it to a String 919 subnet, hasSubnet := d.GetOk("subnet_id") 920 subnetID := subnet.(string) 921 922 // Placement is used for aws_instance; SpotPlacement is used for 923 // aws_spot_instance_request. They represent the same data. :-| 924 opts.Placement = &ec2.Placement{ 925 AvailabilityZone: aws.String(d.Get("availability_zone").(string)), 926 GroupName: aws.String(d.Get("placement_group").(string)), 927 } 928 929 opts.SpotPlacement = &ec2.SpotPlacement{ 930 AvailabilityZone: aws.String(d.Get("availability_zone").(string)), 931 GroupName: aws.String(d.Get("placement_group").(string)), 932 } 933 934 if v := d.Get("tenancy").(string); v != "" { 935 opts.Placement.Tenancy = aws.String(v) 936 } 937 938 associatePublicIPAddress := d.Get("associate_public_ip_address").(bool) 939 940 var groups []*string 941 if v := d.Get("security_groups"); v != nil { 942 // Security group names. 943 // For a nondefault VPC, you must use security group IDs instead. 944 // See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html 945 sgs := v.(*schema.Set).List() 946 if len(sgs) > 0 && hasSubnet { 947 log.Printf("[WARN] Deprecated. Attempting to use 'security_groups' within a VPC instance. Use 'vpc_security_group_ids' instead.") 948 } 949 for _, v := range sgs { 950 str := v.(string) 951 groups = append(groups, aws.String(str)) 952 } 953 } 954 955 if hasSubnet && associatePublicIPAddress { 956 // If we have a non-default VPC / Subnet specified, we can flag 957 // AssociatePublicIpAddress to get a Public IP assigned. By default these are not provided. 958 // You cannot specify both SubnetId and the NetworkInterface.0.* parameters though, otherwise 959 // you get: Network interfaces and an instance-level subnet ID may not be specified on the same request 960 // You also need to attach Security Groups to the NetworkInterface instead of the instance, 961 // to avoid: Network interfaces and an instance-level security groups may not be specified on 962 // the same request 963 ni := &ec2.InstanceNetworkInterfaceSpecification{ 964 AssociatePublicIPAddress: aws.Boolean(associatePublicIPAddress), 965 DeviceIndex: aws.Long(int64(0)), 966 SubnetID: aws.String(subnetID), 967 Groups: groups, 968 } 969 970 if v, ok := d.GetOk("private_ip"); ok { 971 ni.PrivateIPAddress = aws.String(v.(string)) 972 } 973 974 if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 { 975 for _, v := range v.List() { 976 ni.Groups = append(ni.Groups, aws.String(v.(string))) 977 } 978 } 979 980 opts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{ni} 981 } else { 982 if subnetID != "" { 983 opts.SubnetID = aws.String(subnetID) 984 } 985 986 if v, ok := d.GetOk("private_ip"); ok { 987 opts.PrivateIPAddress = aws.String(v.(string)) 988 } 989 if opts.SubnetID != nil && 990 *opts.SubnetID != "" { 991 opts.SecurityGroupIDs = groups 992 } else { 993 opts.SecurityGroups = groups 994 } 995 996 if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 { 997 for _, v := range v.List() { 998 opts.SecurityGroupIDs = append(opts.SecurityGroupIDs, aws.String(v.(string))) 999 } 1000 } 1001 } 1002 1003 if v, ok := d.GetOk("key_name"); ok { 1004 opts.KeyName = aws.String(v.(string)) 1005 } 1006 1007 blockDevices, err := readBlockDeviceMappingsFromConfig(d, conn) 1008 if err != nil { 1009 return nil, err 1010 } 1011 if len(blockDevices) > 0 { 1012 opts.BlockDeviceMappings = blockDevices 1013 } 1014 1015 return opts, nil 1016 } 1017 1018 func awsTerminateInstance(conn *ec2.EC2, id string) error { 1019 log.Printf("[INFO] Terminating instance: %s", id) 1020 req := &ec2.TerminateInstancesInput{ 1021 InstanceIDs: []*string{aws.String(id)}, 1022 } 1023 if _, err := conn.TerminateInstances(req); err != nil { 1024 return fmt.Errorf("Error terminating instance: %s", err) 1025 } 1026 1027 log.Printf("[DEBUG] Waiting for instance (%s) to become terminated", id) 1028 1029 stateConf := &resource.StateChangeConf{ 1030 Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"}, 1031 Target: "terminated", 1032 Refresh: InstanceStateRefreshFunc(conn, id), 1033 Timeout: 10 * time.Minute, 1034 Delay: 10 * time.Second, 1035 MinTimeout: 3 * time.Second, 1036 } 1037 1038 _, err := stateConf.WaitForState() 1039 if err != nil { 1040 return fmt.Errorf( 1041 "Error waiting for instance (%s) to terminate: %s", id, err) 1042 } 1043 1044 return nil 1045 }