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