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