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