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