github.com/anfernee/terraform@v0.6.16-0.20160430000239-06e5085a92f2/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 if err := d.Set("security_groups", []string{}); err != nil { 537 return err 538 } 539 } else { 540 for _, sg := range instance.SecurityGroups { 541 sgs = append(sgs, *sg.GroupName) 542 } 543 log.Printf("[DEBUG] Setting Security Group Names: %#v", sgs) 544 if err := d.Set("security_groups", sgs); err != nil { 545 return err 546 } 547 if err := d.Set("vpc_security_group_ids", []string{}); err != nil { 548 return err 549 } 550 } 551 552 if err := readBlockDevices(d, instance, conn); err != nil { 553 return err 554 } 555 if _, ok := d.GetOk("ephemeral_block_device"); !ok { 556 d.Set("ephemeral_block_device", []interface{}{}) 557 } 558 559 // Instance attributes 560 { 561 attr, err := conn.DescribeInstanceAttribute(&ec2.DescribeInstanceAttributeInput{ 562 Attribute: aws.String("disableApiTermination"), 563 InstanceId: aws.String(d.Id()), 564 }) 565 if err != nil { 566 return err 567 } 568 d.Set("disable_api_termination", attr.DisableApiTermination.Value) 569 } 570 571 return nil 572 } 573 574 func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error { 575 conn := meta.(*AWSClient).ec2conn 576 577 d.Partial(true) 578 if err := setTags(conn, d); err != nil { 579 return err 580 } else { 581 d.SetPartial("tags") 582 } 583 584 // SourceDestCheck can only be set on VPC instances 585 // AWS will return an error of InvalidParameterCombination if we attempt 586 // to modify the source_dest_check of an instance in EC2 Classic 587 log.Printf("[INFO] Modifying instance %s", d.Id()) 588 _, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{ 589 InstanceId: aws.String(d.Id()), 590 SourceDestCheck: &ec2.AttributeBooleanValue{ 591 Value: aws.Bool(d.Get("source_dest_check").(bool)), 592 }, 593 }) 594 if err != nil { 595 if ec2err, ok := err.(awserr.Error); ok { 596 // Toloerate InvalidParameterCombination error in Classic, otherwise 597 // return the error 598 if "InvalidParameterCombination" != ec2err.Code() { 599 return err 600 } 601 log.Printf("[WARN] Attempted to modify SourceDestCheck on non VPC instance: %s", ec2err.Message()) 602 } 603 } 604 605 if d.HasChange("vpc_security_group_ids") { 606 var groups []*string 607 if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 { 608 for _, v := range v.List() { 609 groups = append(groups, aws.String(v.(string))) 610 } 611 } 612 _, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{ 613 InstanceId: aws.String(d.Id()), 614 Groups: groups, 615 }) 616 if err != nil { 617 return err 618 } 619 } 620 621 if d.HasChange("disable_api_termination") { 622 _, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{ 623 InstanceId: aws.String(d.Id()), 624 DisableApiTermination: &ec2.AttributeBooleanValue{ 625 Value: aws.Bool(d.Get("disable_api_termination").(bool)), 626 }, 627 }) 628 if err != nil { 629 return err 630 } 631 } 632 633 if d.HasChange("instance_initiated_shutdown_behavior") { 634 log.Printf("[INFO] Modifying instance %s", d.Id()) 635 _, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{ 636 InstanceId: aws.String(d.Id()), 637 InstanceInitiatedShutdownBehavior: &ec2.AttributeValue{ 638 Value: aws.String(d.Get("instance_initiated_shutdown_behavior").(string)), 639 }, 640 }) 641 if err != nil { 642 return err 643 } 644 } 645 646 if d.HasChange("monitoring") { 647 var mErr error 648 if d.Get("monitoring").(bool) { 649 log.Printf("[DEBUG] Enabling monitoring for Instance (%s)", d.Id()) 650 _, mErr = conn.MonitorInstances(&ec2.MonitorInstancesInput{ 651 InstanceIds: []*string{aws.String(d.Id())}, 652 }) 653 } else { 654 log.Printf("[DEBUG] Disabling monitoring for Instance (%s)", d.Id()) 655 _, mErr = conn.UnmonitorInstances(&ec2.UnmonitorInstancesInput{ 656 InstanceIds: []*string{aws.String(d.Id())}, 657 }) 658 } 659 if mErr != nil { 660 return fmt.Errorf("[WARN] Error updating Instance monitoring: %s", mErr) 661 } 662 } 663 664 // TODO(mitchellh): wait for the attributes we modified to 665 // persist the change... 666 667 d.Partial(false) 668 669 return resourceAwsInstanceRead(d, meta) 670 } 671 672 func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error { 673 conn := meta.(*AWSClient).ec2conn 674 675 if err := awsTerminateInstance(conn, d.Id()); err != nil { 676 return err 677 } 678 679 d.SetId("") 680 return nil 681 } 682 683 // InstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 684 // an EC2 instance. 685 func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc { 686 return func() (interface{}, string, error) { 687 resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{ 688 InstanceIds: []*string{aws.String(instanceID)}, 689 }) 690 if err != nil { 691 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidInstanceID.NotFound" { 692 // Set this to nil as if we didn't find anything. 693 resp = nil 694 } else { 695 log.Printf("Error on InstanceStateRefresh: %s", err) 696 return nil, "", err 697 } 698 } 699 700 if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 { 701 // Sometimes AWS just has consistency issues and doesn't see 702 // our instance yet. Return an empty state. 703 return nil, "", nil 704 } 705 706 i := resp.Reservations[0].Instances[0] 707 return i, *i.State.Name, nil 708 } 709 } 710 711 func readBlockDevices(d *schema.ResourceData, instance *ec2.Instance, conn *ec2.EC2) error { 712 ibds, err := readBlockDevicesFromInstance(instance, conn) 713 if err != nil { 714 return err 715 } 716 717 if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil { 718 return err 719 } 720 721 // This handles the import case which needs to be defaulted to empty 722 if _, ok := d.GetOk("root_block_device"); !ok { 723 if err := d.Set("root_block_device", []interface{}{}); err != nil { 724 return err 725 } 726 } 727 728 if ibds["root"] != nil { 729 roots := []interface{}{ibds["root"]} 730 if err := d.Set("root_block_device", roots); err != nil { 731 return err 732 } 733 } 734 735 return nil 736 } 737 738 func readBlockDevicesFromInstance(instance *ec2.Instance, conn *ec2.EC2) (map[string]interface{}, error) { 739 blockDevices := make(map[string]interface{}) 740 blockDevices["ebs"] = make([]map[string]interface{}, 0) 741 blockDevices["root"] = nil 742 743 instanceBlockDevices := make(map[string]*ec2.InstanceBlockDeviceMapping) 744 for _, bd := range instance.BlockDeviceMappings { 745 if bd.Ebs != nil { 746 instanceBlockDevices[*bd.Ebs.VolumeId] = bd 747 } 748 } 749 750 if len(instanceBlockDevices) == 0 { 751 return nil, nil 752 } 753 754 volIDs := make([]*string, 0, len(instanceBlockDevices)) 755 for volID := range instanceBlockDevices { 756 volIDs = append(volIDs, aws.String(volID)) 757 } 758 759 // Need to call DescribeVolumes to get volume_size and volume_type for each 760 // EBS block device 761 volResp, err := conn.DescribeVolumes(&ec2.DescribeVolumesInput{ 762 VolumeIds: volIDs, 763 }) 764 if err != nil { 765 return nil, err 766 } 767 768 for _, vol := range volResp.Volumes { 769 instanceBd := instanceBlockDevices[*vol.VolumeId] 770 bd := make(map[string]interface{}) 771 772 if instanceBd.Ebs != nil && instanceBd.Ebs.DeleteOnTermination != nil { 773 bd["delete_on_termination"] = *instanceBd.Ebs.DeleteOnTermination 774 } 775 if vol.Size != nil { 776 bd["volume_size"] = *vol.Size 777 } 778 if vol.VolumeType != nil { 779 bd["volume_type"] = *vol.VolumeType 780 } 781 if vol.Iops != nil { 782 bd["iops"] = *vol.Iops 783 } 784 785 if blockDeviceIsRoot(instanceBd, instance) { 786 blockDevices["root"] = bd 787 } else { 788 if instanceBd.DeviceName != nil { 789 bd["device_name"] = *instanceBd.DeviceName 790 } 791 if vol.Encrypted != nil { 792 bd["encrypted"] = *vol.Encrypted 793 } 794 if vol.SnapshotId != nil { 795 bd["snapshot_id"] = *vol.SnapshotId 796 } 797 798 blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd) 799 } 800 } 801 802 return blockDevices, nil 803 } 804 805 func blockDeviceIsRoot(bd *ec2.InstanceBlockDeviceMapping, instance *ec2.Instance) bool { 806 return bd.DeviceName != nil && 807 instance.RootDeviceName != nil && 808 *bd.DeviceName == *instance.RootDeviceName 809 } 810 811 func fetchRootDeviceName(ami string, conn *ec2.EC2) (*string, error) { 812 if ami == "" { 813 return nil, fmt.Errorf("Cannot fetch root device name for blank AMI ID.") 814 } 815 816 log.Printf("[DEBUG] Describing AMI %q to get root block device name", ami) 817 res, err := conn.DescribeImages(&ec2.DescribeImagesInput{ 818 ImageIds: []*string{aws.String(ami)}, 819 }) 820 if err != nil { 821 return nil, err 822 } 823 824 // For a bad image, we just return nil so we don't block a refresh 825 if len(res.Images) == 0 { 826 return nil, nil 827 } 828 829 image := res.Images[0] 830 rootDeviceName := image.RootDeviceName 831 832 // Some AMIs have a RootDeviceName like "/dev/sda1" that does not appear as a 833 // DeviceName in the BlockDeviceMapping list (which will instead have 834 // something like "/dev/sda") 835 // 836 // While this seems like it breaks an invariant of AMIs, it ends up working 837 // on the AWS side, and AMIs like this are common enough that we need to 838 // special case it so Terraform does the right thing. 839 // 840 // Our heuristic is: if the RootDeviceName does not appear in the 841 // BlockDeviceMapping, assume that the DeviceName of the first 842 // BlockDeviceMapping entry serves as the root device. 843 rootDeviceNameInMapping := false 844 for _, bdm := range image.BlockDeviceMappings { 845 if bdm.DeviceName == image.RootDeviceName { 846 rootDeviceNameInMapping = true 847 } 848 } 849 850 if !rootDeviceNameInMapping && len(image.BlockDeviceMappings) > 0 { 851 rootDeviceName = image.BlockDeviceMappings[0].DeviceName 852 } 853 854 if rootDeviceName == nil { 855 return nil, fmt.Errorf("[WARN] Error finding Root Device Name for AMI (%s)", ami) 856 } 857 858 return rootDeviceName, nil 859 } 860 861 func readBlockDeviceMappingsFromConfig( 862 d *schema.ResourceData, conn *ec2.EC2) ([]*ec2.BlockDeviceMapping, error) { 863 blockDevices := make([]*ec2.BlockDeviceMapping, 0) 864 865 if v, ok := d.GetOk("ebs_block_device"); ok { 866 vL := v.(*schema.Set).List() 867 for _, v := range vL { 868 bd := v.(map[string]interface{}) 869 ebs := &ec2.EbsBlockDevice{ 870 DeleteOnTermination: aws.Bool(bd["delete_on_termination"].(bool)), 871 } 872 873 if v, ok := bd["snapshot_id"].(string); ok && v != "" { 874 ebs.SnapshotId = aws.String(v) 875 } 876 877 if v, ok := bd["encrypted"].(bool); ok && v { 878 ebs.Encrypted = aws.Bool(v) 879 } 880 881 if v, ok := bd["volume_size"].(int); ok && v != 0 { 882 ebs.VolumeSize = aws.Int64(int64(v)) 883 } 884 885 if v, ok := bd["volume_type"].(string); ok && v != "" { 886 ebs.VolumeType = aws.String(v) 887 } 888 889 if v, ok := bd["iops"].(int); ok && v > 0 { 890 ebs.Iops = aws.Int64(int64(v)) 891 } 892 893 blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{ 894 DeviceName: aws.String(bd["device_name"].(string)), 895 Ebs: ebs, 896 }) 897 } 898 } 899 900 if v, ok := d.GetOk("ephemeral_block_device"); ok { 901 vL := v.(*schema.Set).List() 902 for _, v := range vL { 903 bd := v.(map[string]interface{}) 904 blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{ 905 DeviceName: aws.String(bd["device_name"].(string)), 906 VirtualName: aws.String(bd["virtual_name"].(string)), 907 }) 908 } 909 } 910 911 if v, ok := d.GetOk("root_block_device"); ok { 912 vL := v.(*schema.Set).List() 913 if len(vL) > 1 { 914 return nil, fmt.Errorf("Cannot specify more than one root_block_device.") 915 } 916 for _, v := range vL { 917 bd := v.(map[string]interface{}) 918 ebs := &ec2.EbsBlockDevice{ 919 DeleteOnTermination: aws.Bool(bd["delete_on_termination"].(bool)), 920 } 921 922 if v, ok := bd["volume_size"].(int); ok && v != 0 { 923 ebs.VolumeSize = aws.Int64(int64(v)) 924 } 925 926 if v, ok := bd["volume_type"].(string); ok && v != "" { 927 ebs.VolumeType = aws.String(v) 928 } 929 930 if v, ok := bd["iops"].(int); ok && v > 0 { 931 ebs.Iops = aws.Int64(int64(v)) 932 } 933 934 if dn, err := fetchRootDeviceName(d.Get("ami").(string), conn); err == nil { 935 if dn == nil { 936 return nil, fmt.Errorf( 937 "Expected 1 AMI for ID: %s, got none", 938 d.Get("ami").(string)) 939 } 940 941 blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{ 942 DeviceName: dn, 943 Ebs: ebs, 944 }) 945 } else { 946 return nil, err 947 } 948 } 949 } 950 951 return blockDevices, nil 952 } 953 954 type awsInstanceOpts struct { 955 BlockDeviceMappings []*ec2.BlockDeviceMapping 956 DisableAPITermination *bool 957 EBSOptimized *bool 958 Monitoring *ec2.RunInstancesMonitoringEnabled 959 IAMInstanceProfile *ec2.IamInstanceProfileSpecification 960 ImageID *string 961 InstanceInitiatedShutdownBehavior *string 962 InstanceType *string 963 KeyName *string 964 NetworkInterfaces []*ec2.InstanceNetworkInterfaceSpecification 965 Placement *ec2.Placement 966 PrivateIPAddress *string 967 SecurityGroupIDs []*string 968 SecurityGroups []*string 969 SpotPlacement *ec2.SpotPlacement 970 SubnetID *string 971 UserData64 *string 972 } 973 974 func buildAwsInstanceOpts( 975 d *schema.ResourceData, meta interface{}) (*awsInstanceOpts, error) { 976 conn := meta.(*AWSClient).ec2conn 977 978 opts := &awsInstanceOpts{ 979 DisableAPITermination: aws.Bool(d.Get("disable_api_termination").(bool)), 980 EBSOptimized: aws.Bool(d.Get("ebs_optimized").(bool)), 981 ImageID: aws.String(d.Get("ami").(string)), 982 InstanceType: aws.String(d.Get("instance_type").(string)), 983 } 984 985 if v := d.Get("instance_initiated_shutdown_behavior").(string); v != "" { 986 opts.InstanceInitiatedShutdownBehavior = aws.String(v) 987 } 988 989 opts.Monitoring = &ec2.RunInstancesMonitoringEnabled{ 990 Enabled: aws.Bool(d.Get("monitoring").(bool)), 991 } 992 993 opts.IAMInstanceProfile = &ec2.IamInstanceProfileSpecification{ 994 Name: aws.String(d.Get("iam_instance_profile").(string)), 995 } 996 997 user_data := d.Get("user_data").(string) 998 999 // Check whether the user_data is already Base64 encoded; don't double-encode 1000 _, base64DecodeError := base64.StdEncoding.DecodeString(user_data) 1001 1002 if base64DecodeError == nil { 1003 opts.UserData64 = aws.String(user_data) 1004 } else { 1005 opts.UserData64 = aws.String(base64.StdEncoding.EncodeToString([]byte(user_data))) 1006 } 1007 1008 // check for non-default Subnet, and cast it to a String 1009 subnet, hasSubnet := d.GetOk("subnet_id") 1010 subnetID := subnet.(string) 1011 1012 // Placement is used for aws_instance; SpotPlacement is used for 1013 // aws_spot_instance_request. They represent the same data. :-| 1014 opts.Placement = &ec2.Placement{ 1015 AvailabilityZone: aws.String(d.Get("availability_zone").(string)), 1016 GroupName: aws.String(d.Get("placement_group").(string)), 1017 } 1018 1019 opts.SpotPlacement = &ec2.SpotPlacement{ 1020 AvailabilityZone: aws.String(d.Get("availability_zone").(string)), 1021 GroupName: aws.String(d.Get("placement_group").(string)), 1022 } 1023 1024 if v := d.Get("tenancy").(string); v != "" { 1025 opts.Placement.Tenancy = aws.String(v) 1026 } 1027 1028 associatePublicIPAddress := d.Get("associate_public_ip_address").(bool) 1029 1030 var groups []*string 1031 if v := d.Get("security_groups"); v != nil { 1032 // Security group names. 1033 // For a nondefault VPC, you must use security group IDs instead. 1034 // See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html 1035 sgs := v.(*schema.Set).List() 1036 if len(sgs) > 0 && hasSubnet { 1037 log.Printf("[WARN] Deprecated. Attempting to use 'security_groups' within a VPC instance. Use 'vpc_security_group_ids' instead.") 1038 } 1039 for _, v := range sgs { 1040 str := v.(string) 1041 groups = append(groups, aws.String(str)) 1042 } 1043 } 1044 1045 if hasSubnet && associatePublicIPAddress { 1046 // If we have a non-default VPC / Subnet specified, we can flag 1047 // AssociatePublicIpAddress to get a Public IP assigned. By default these are not provided. 1048 // You cannot specify both SubnetId and the NetworkInterface.0.* parameters though, otherwise 1049 // you get: Network interfaces and an instance-level subnet ID may not be specified on the same request 1050 // You also need to attach Security Groups to the NetworkInterface instead of the instance, 1051 // to avoid: Network interfaces and an instance-level security groups may not be specified on 1052 // the same request 1053 ni := &ec2.InstanceNetworkInterfaceSpecification{ 1054 AssociatePublicIpAddress: aws.Bool(associatePublicIPAddress), 1055 DeviceIndex: aws.Int64(int64(0)), 1056 SubnetId: aws.String(subnetID), 1057 Groups: groups, 1058 } 1059 1060 if v, ok := d.GetOk("private_ip"); ok { 1061 ni.PrivateIpAddress = aws.String(v.(string)) 1062 } 1063 1064 if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 { 1065 for _, v := range v.List() { 1066 ni.Groups = append(ni.Groups, aws.String(v.(string))) 1067 } 1068 } 1069 1070 opts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{ni} 1071 } else { 1072 if subnetID != "" { 1073 opts.SubnetID = aws.String(subnetID) 1074 } 1075 1076 if v, ok := d.GetOk("private_ip"); ok { 1077 opts.PrivateIPAddress = aws.String(v.(string)) 1078 } 1079 if opts.SubnetID != nil && 1080 *opts.SubnetID != "" { 1081 opts.SecurityGroupIDs = groups 1082 } else { 1083 opts.SecurityGroups = groups 1084 } 1085 1086 if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 { 1087 for _, v := range v.List() { 1088 opts.SecurityGroupIDs = append(opts.SecurityGroupIDs, aws.String(v.(string))) 1089 } 1090 } 1091 } 1092 1093 if v, ok := d.GetOk("key_name"); ok { 1094 opts.KeyName = aws.String(v.(string)) 1095 } 1096 1097 blockDevices, err := readBlockDeviceMappingsFromConfig(d, conn) 1098 if err != nil { 1099 return nil, err 1100 } 1101 if len(blockDevices) > 0 { 1102 opts.BlockDeviceMappings = blockDevices 1103 } 1104 1105 return opts, nil 1106 } 1107 1108 func awsTerminateInstance(conn *ec2.EC2, id string) error { 1109 log.Printf("[INFO] Terminating instance: %s", id) 1110 req := &ec2.TerminateInstancesInput{ 1111 InstanceIds: []*string{aws.String(id)}, 1112 } 1113 if _, err := conn.TerminateInstances(req); err != nil { 1114 return fmt.Errorf("Error terminating instance: %s", err) 1115 } 1116 1117 log.Printf("[DEBUG] Waiting for instance (%s) to become terminated", id) 1118 1119 stateConf := &resource.StateChangeConf{ 1120 Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"}, 1121 Target: []string{"terminated"}, 1122 Refresh: InstanceStateRefreshFunc(conn, id), 1123 Timeout: 10 * time.Minute, 1124 Delay: 10 * time.Second, 1125 MinTimeout: 3 * time.Second, 1126 } 1127 1128 _, err := stateConf.WaitForState() 1129 if err != nil { 1130 return fmt.Errorf( 1131 "Error waiting for instance (%s) to terminate: %s", id, err) 1132 } 1133 1134 return nil 1135 } 1136 1137 func iamInstanceProfileArnToName(ip *ec2.IamInstanceProfile) string { 1138 if ip == nil || ip.Arn == nil { 1139 return "" 1140 } 1141 parts := strings.Split(*ip.Arn, "/") 1142 return parts[len(parts)-1] 1143 }