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