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