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