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