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