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