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