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