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