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