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