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