diff --git a/aws/resource_aws_instance.go b/aws/resource_aws_instance.go index 2ff3c7cbb51..925861a4489 100644 --- a/aws/resource_aws_instance.go +++ b/aws/resource_aws_instance.go @@ -52,295 +52,72 @@ func resourceAwsInstance() *schema.Resource { Required: true, ForceNew: true, }, - "arn": { Type: schema.TypeString, Computed: true, }, - "associate_public_ip_address": { Type: schema.TypeBool, ForceNew: true, Computed: true, Optional: true, }, - "availability_zone": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - - "placement_group": { - Type: schema.TypeString, + "cpu_core_count": { + Type: schema.TypeInt, Optional: true, Computed: true, ForceNew: true, }, - - "instance_type": { - Type: schema.TypeString, - Required: true, - }, - - "key_name": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Computed: true, - }, - - "get_password_data": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - - "password_data": { - Type: schema.TypeString, - Computed: true, - }, - - "subnet_id": { - Type: schema.TypeString, + "cpu_threads_per_core": { + Type: schema.TypeInt, Optional: true, Computed: true, ForceNew: true, }, - - "private_ip": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Computed: true, - ValidateFunc: validation.Any( - validation.StringIsEmpty, - validation.IsIPv4Address, - ), - }, - - "secondary_private_ips": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.IsIPv4Address, - }, - }, - - "source_dest_check": { - Type: schema.TypeBool, + "credit_specification": { + Type: schema.TypeList, Optional: true, - Default: true, - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // Suppress diff if network_interface is set - _, ok := d.GetOk("network_interface") - return ok - }, - }, - - "user_data": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"user_data_base64"}, + MaxItems: 1, DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // Sometimes the EC2 API responds with the equivalent, empty SHA1 sum - // echo -n "" | shasum - if (old == "da39a3ee5e6b4b0d3255bfef95601890afd80709" && new == "") || - (old == "" && new == "da39a3ee5e6b4b0d3255bfef95601890afd80709") { + if old == "1" && new == "0" { return true } return false }, - StateFunc: func(v interface{}) string { - switch v := v.(type) { - case string: - return userDataHashSum(v) - default: - return "" - } - }, - ValidateFunc: validation.StringLenBetween(0, 16384), - }, - - "user_data_base64": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"user_data"}, - ValidateFunc: func(v interface{}, name string) (warns []string, errs []error) { - s := v.(string) - if !isBase64Encoded([]byte(s)) { - errs = append(errs, fmt.Errorf( - "%s: must be base64-encoded", name, - )) - } - return - }, - }, - - "security_groups": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - ForceNew: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - }, - - "vpc_security_group_ids": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - }, - - "public_dns": { - Type: schema.TypeString, - Computed: true, - }, - - "outpost_arn": { - Type: schema.TypeString, - Computed: true, - }, - - "primary_network_interface_id": { - Type: schema.TypeString, - Computed: true, - }, - - "network_interface": { - ConflictsWith: []string{"associate_public_ip_address", "subnet_id", "private_ip", "secondary_private_ips", "vpc_security_group_ids", "security_groups", "ipv6_addresses", "ipv6_address_count", "source_dest_check"}, - Type: schema.TypeSet, - Optional: true, - Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "delete_on_termination": { - Type: schema.TypeBool, - Default: false, - Optional: true, - ForceNew: true, - }, - "network_interface_id": { + "cpu_credits": { Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "device_index": { - Type: schema.TypeInt, - Required: true, - ForceNew: true, + Optional: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // Only work with existing instances + if d.Id() == "" { + return false + } + // Only work with missing configurations + if new != "" { + return false + } + // Only work when already set in Terraform state + if old == "" { + return false + } + return true + }, }, }, }, }, - - "public_ip": { - Type: schema.TypeString, - Computed: true, - }, - - "instance_state": { - Type: schema.TypeString, - Computed: true, - }, - - "private_dns": { - Type: schema.TypeString, - Computed: true, - }, - - "ebs_optimized": { - Type: schema.TypeBool, - Optional: true, - ForceNew: true, - }, - "disable_api_termination": { Type: schema.TypeBool, Optional: true, }, - - "instance_initiated_shutdown_behavior": { - Type: schema.TypeString, - Optional: true, - }, - - "hibernation": { - Type: schema.TypeBool, - Optional: true, - ForceNew: true, - }, - - "monitoring": { - Type: schema.TypeBool, - Optional: true, - }, - - "iam_instance_profile": { - Type: schema.TypeString, - Optional: true, - }, - - "ipv6_address_count": { - Type: schema.TypeInt, - Optional: true, - ForceNew: true, - Computed: true, - }, - - "ipv6_addresses": { - Type: schema.TypeList, - Optional: true, - Computed: true, - ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.IsIPv6Address, - }, - }, - - "tenancy": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - ec2.TenancyDedicated, - ec2.TenancyDefault, - ec2.TenancyHost, - }, false), - }, - "host_id": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - }, - "cpu_core_count": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - ForceNew: true, - }, - - "cpu_threads_per_core": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - ForceNew: true, - }, - - "tags": tagsSchema(), - - "volume_tags": tagsSchemaComputed(), - "ebs_block_device": { Type: schema.TypeSet, Optional: true, @@ -353,20 +130,17 @@ func resourceAwsInstance() *schema.Resource { Default: true, ForceNew: true, }, - "device_name": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "encrypted": { Type: schema.TypeBool, Optional: true, Computed: true, ForceNew: true, }, - "iops": { Type: schema.TypeInt, Optional: true, @@ -374,21 +148,19 @@ func resourceAwsInstance() *schema.Resource { ForceNew: true, DiffSuppressFunc: iopsDiffSuppressFunc, }, - "kms_key_id": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "snapshot_id": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - + "tags": tagsSchemaConflictsWith([]string{"volume_tags"}), "throughput": { Type: schema.TypeInt, Optional: true, @@ -396,14 +168,16 @@ func resourceAwsInstance() *schema.Resource { ForceNew: true, DiffSuppressFunc: throughputDiffSuppressFunc, }, - + "volume_id": { + Type: schema.TypeString, + Computed: true, + }, "volume_size": { Type: schema.TypeInt, Optional: true, Computed: true, ForceNew: true, }, - "volume_type": { Type: schema.TypeString, Optional: true, @@ -411,11 +185,6 @@ func resourceAwsInstance() *schema.Resource { ForceNew: true, ValidateFunc: validation.StringInSlice(ec2.VolumeType_Values(), false), }, - - "volume_id": { - Type: schema.TypeString, - Computed: true, - }, }, }, Set: func(v interface{}) int { @@ -426,7 +195,27 @@ func resourceAwsInstance() *schema.Resource { return hashcode.String(buf.String()) }, }, - + "ebs_optimized": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "enclave_options": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enabled": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + }, + }, "ephemeral_block_device": { Type: schema.TypeSet, Optional: true, @@ -438,16 +227,14 @@ func resourceAwsInstance() *schema.Resource { Type: schema.TypeString, Required: true, }, - - "virtual_name": { - Type: schema.TypeString, - Optional: true, - }, - "no_device": { Type: schema.TypeBool, Optional: true, }, + "virtual_name": { + Type: schema.TypeString, + Optional: true, + }, }, }, Set: func(v interface{}) int { @@ -461,7 +248,158 @@ func resourceAwsInstance() *schema.Resource { return hashcode.String(buf.String()) }, }, - + "get_password_data": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "hibernation": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "host_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "iam_instance_profile": { + Type: schema.TypeString, + Optional: true, + }, + "instance_initiated_shutdown_behavior": { + Type: schema.TypeString, + Optional: true, + }, + "instance_state": { + Type: schema.TypeString, + Computed: true, + }, + "instance_type": { + Type: schema.TypeString, + Required: true, + }, + "ipv6_address_count": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + Computed: true, + }, + "ipv6_addresses": { + Type: schema.TypeList, + Optional: true, + Computed: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.IsIPv6Address, + }, + }, + "key_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "metadata_options": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "http_endpoint": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ec2.InstanceMetadataEndpointStateEnabled, ec2.InstanceMetadataEndpointStateDisabled}, false), + }, + "http_put_response_hop_limit": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: validation.IntBetween(1, 64), + }, + "http_tokens": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ec2.HttpTokensStateOptional, ec2.HttpTokensStateRequired}, false), + }, + }, + }, + }, + "monitoring": { + Type: schema.TypeBool, + Optional: true, + }, + "network_interface": { + ConflictsWith: []string{"associate_public_ip_address", "subnet_id", "private_ip", "secondary_private_ips", "vpc_security_group_ids", "security_groups", "ipv6_addresses", "ipv6_address_count", "source_dest_check"}, + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "delete_on_termination": { + Type: schema.TypeBool, + Default: false, + Optional: true, + ForceNew: true, + }, + "device_index": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "network_interface_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + }, + }, + "outpost_arn": { + Type: schema.TypeString, + Computed: true, + }, + "password_data": { + Type: schema.TypeString, + Computed: true, + }, + "placement_group": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "primary_network_interface_id": { + Type: schema.TypeString, + Computed: true, + }, + "private_dns": { + Type: schema.TypeString, + Computed: true, + }, + "private_ip": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + ValidateFunc: validation.Any( + validation.StringIsEmpty, + validation.IsIPv4Address, + ), + }, + "public_dns": { + Type: schema.TypeString, + Computed: true, + }, + "public_ip": { + Type: schema.TypeString, + Computed: true, + }, "root_block_device": { Type: schema.TypeList, Optional: true, @@ -470,147 +408,152 @@ func resourceAwsInstance() *schema.Resource { Elem: &schema.Resource{ // "You can only modify the volume size, volume type, and Delete on // Termination flag on the block device mapping entry for the root - // device volume." - bit.ly/ec2bdmap + // device volume." + // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html Schema: map[string]*schema.Schema{ "delete_on_termination": { Type: schema.TypeBool, Optional: true, Default: true, }, - "device_name": { Type: schema.TypeString, Computed: true, }, - "encrypted": { Type: schema.TypeBool, Optional: true, Computed: true, ForceNew: true, }, - - "kms_key_id": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - }, - "iops": { Type: schema.TypeInt, Optional: true, Computed: true, DiffSuppressFunc: iopsDiffSuppressFunc, }, - + "kms_key_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "tags": tagsSchemaConflictsWith([]string{"volume_tags"}), "throughput": { Type: schema.TypeInt, Optional: true, Computed: true, DiffSuppressFunc: throughputDiffSuppressFunc, }, - + "volume_id": { + Type: schema.TypeString, + Computed: true, + }, "volume_size": { Type: schema.TypeInt, Optional: true, Computed: true, }, - "volume_type": { Type: schema.TypeString, Optional: true, Computed: true, ValidateFunc: validation.StringInSlice(ec2.VolumeType_Values(), false), }, - - "volume_id": { - Type: schema.TypeString, - Computed: true, - }, }, }, }, - - "credit_specification": { - Type: schema.TypeList, + "secondary_private_ips": { + Type: schema.TypeSet, Optional: true, - MaxItems: 1, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.IsIPv4Address, + }, + }, + "security_groups": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + "source_dest_check": { + Type: schema.TypeBool, + Optional: true, + Default: true, DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - if old == "1" && new == "0" { + // Suppress diff if network_interface is set + _, ok := d.GetOk("network_interface") + return ok + }, + }, + "subnet_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "tags": tagsSchema(), + "tenancy": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + ec2.TenancyDedicated, + ec2.TenancyDefault, + ec2.TenancyHost, + }, false), + }, + "user_data": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"user_data_base64"}, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // Sometimes the EC2 API responds with the equivalent, empty SHA1 sum + // echo -n "" | shasum + if (old == "da39a3ee5e6b4b0d3255bfef95601890afd80709" && new == "") || + (old == "" && new == "da39a3ee5e6b4b0d3255bfef95601890afd80709") { return true } return false }, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "cpu_credits": { - Type: schema.TypeString, - Optional: true, - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // Only work with existing instances - if d.Id() == "" { - return false - } - // Only work with missing configurations - if new != "" { - return false - } - // Only work when already set in Terraform state - if old == "" { - return false - } - return true - }, - }, - }, + StateFunc: func(v interface{}) string { + switch v := v.(type) { + case string: + return userDataHashSum(v) + default: + return "" + } }, + ValidateFunc: validation.StringLenBetween(0, 16384), }, - - "metadata_options": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "http_endpoint": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice([]string{ec2.InstanceMetadataEndpointStateEnabled, ec2.InstanceMetadataEndpointStateDisabled}, false), - }, - "http_tokens": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice([]string{ec2.HttpTokensStateOptional, ec2.HttpTokensStateRequired}, false), - }, - "http_put_response_hop_limit": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - ValidateFunc: validation.IntBetween(1, 64), - }, - }, + "user_data_base64": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"user_data"}, + ValidateFunc: func(v interface{}, name string) (warns []string, errs []error) { + s := v.(string) + if !isBase64Encoded([]byte(s)) { + errs = append(errs, fmt.Errorf( + "%s: must be base64-encoded", name, + )) + } + return }, }, - - "enclave_options": { - Type: schema.TypeList, + "volume_tags": tagsSchema(), + "vpc_security_group_ids": { + Type: schema.TypeSet, Optional: true, Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "enabled": { - Type: schema.TypeBool, - Optional: true, - Computed: true, - ForceNew: true, - }, - }, - }, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, }, }, } @@ -762,6 +705,39 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error { }) } + // tags in root_block_device and ebs_block_device + blockDeviceTagsToCreate := map[string]map[string]interface{}{} + if v, ok := d.GetOk("root_block_device"); ok { + vL := v.([]interface{}) + for _, v := range vL { + bd := v.(map[string]interface{}) + if blockDeviceTags, ok := bd["tags"].(map[string]interface{}); ok && len(blockDeviceTags) > 0 { + if rootVolumeId := getRootVolumeId(instance); rootVolumeId != "" { + blockDeviceTagsToCreate[rootVolumeId] = blockDeviceTags + } + } + } + } + + if v, ok := d.GetOk("ebs_block_device"); ok { + vL := v.(*schema.Set).List() + for _, v := range vL { + bd := v.(map[string]interface{}) + if blockDeviceTags, ok := bd["tags"].(map[string]interface{}); ok && len(blockDeviceTags) > 0 { + devName := bd["device_name"].(string) + if volumeId := getVolumeIdByDeviceName(instance, devName); volumeId != "" { + blockDeviceTagsToCreate[volumeId] = blockDeviceTags + } + } + } + } + + for vol, blockDeviceTags := range blockDeviceTagsToCreate { + if err := keyvaluetags.Ec2CreateTags(conn, vol, blockDeviceTags); err != nil { + log.Printf("[ERR] Error creating tags for EBS volume %s: %s", vol, err) + } + } + // Update if we need to return resourceAwsInstanceUpdate(d, meta) } @@ -936,13 +912,15 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("error setting tags: %s", err) } - volumeTags, err := readVolumeTags(conn, d.Id()) - if err != nil { - return err - } + if _, ok := d.GetOk("volume_tags"); ok && !blockDeviceTagsDefined(d) { + volumeTags, err := readVolumeTags(conn, d.Id()) + if err != nil { + return err + } - if err := d.Set("volume_tags", keyvaluetags.Ec2KeyValueTags(volumeTags).IgnoreAws().Map()); err != nil { - return fmt.Errorf("error setting volume_tags: %s", err) + if err := d.Set("volume_tags", keyvaluetags.Ec2KeyValueTags(volumeTags).IgnoreAws().Map()); err != nil { + return fmt.Errorf("error setting volume_tags: %s", err) + } } if err := readSecurityGroups(d, instance, conn); err != nil { @@ -1522,6 +1500,14 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error { } } } + + if d.HasChange("root_block_device.0.tags") { + o, n := d.GetChange("root_block_device.0.tags") + + if err := keyvaluetags.Ec2UpdateTags(conn, volumeID, o, n); err != nil { + return fmt.Errorf("error updating tags for volume (%s): %s", volumeID, err) + } + } } // TODO(mitchellh): wait for the attributes we modified to @@ -1667,7 +1653,7 @@ func stringifyStateReason(sr *ec2.StateReason) string { } func readBlockDevices(d *schema.ResourceData, instance *ec2.Instance, conn *ec2.EC2) error { - ibds, err := readBlockDevicesFromInstance(instance, conn) + ibds, err := readBlockDevicesFromInstance(d, instance, conn) if err != nil { return err } @@ -1743,7 +1729,7 @@ func disassociateInstanceProfile(associationId *string, conn *ec2.EC2) error { return nil } -func readBlockDevicesFromInstance(instance *ec2.Instance, conn *ec2.EC2) (map[string]interface{}, error) { +func readBlockDevicesFromInstance(d *schema.ResourceData, instance *ec2.Instance, conn *ec2.EC2) (map[string]interface{}, error) { blockDevices := make(map[string]interface{}) blockDevices["ebs"] = make([]map[string]interface{}, 0) blockDevices["root"] = nil @@ -1804,6 +1790,9 @@ func readBlockDevicesFromInstance(instance *ec2.Instance, conn *ec2.EC2) (map[st if instanceBd.DeviceName != nil { bd["device_name"] = aws.StringValue(instanceBd.DeviceName) } + if _, ok := d.GetOk("volume_tags"); !ok && vol.Tags != nil { + bd["tags"] = keyvaluetags.Ec2KeyValueTags(vol.Tags).IgnoreAws().Map() + } if blockDeviceIsRoot(instanceBd, instance) { blockDevices["root"] = bd @@ -2521,6 +2510,58 @@ func getAwsInstanceVolumeIds(conn *ec2.EC2, instanceId string) ([]string, error) return volumeIds, nil } +func getRootVolumeId(instance *ec2.Instance) string { + rootVolumeId := "" + for _, bd := range instance.BlockDeviceMappings { + if bd.Ebs != nil && blockDeviceIsRoot(bd, instance) { + if bd.Ebs.VolumeId != nil { + rootVolumeId = aws.StringValue(bd.Ebs.VolumeId) + } + break + } + } + + return rootVolumeId +} + +func getVolumeIdByDeviceName(instance *ec2.Instance, deviceName string) string { + volumeId := "" + for _, bd := range instance.BlockDeviceMappings { + if aws.StringValue(bd.DeviceName) == deviceName { + if bd.Ebs != nil { + volumeId = aws.StringValue(bd.Ebs.VolumeId) + break + } + } + } + + return volumeId +} + +func blockDeviceTagsDefined(d *schema.ResourceData) bool { + if v, ok := d.GetOk("root_block_device"); ok { + vL := v.([]interface{}) + for _, v := range vL { + bd := v.(map[string]interface{}) + if blockDeviceTags, ok := bd["tags"].(map[string]interface{}); ok && len(blockDeviceTags) > 0 { + return true + } + } + } + + if v, ok := d.GetOk("ebs_block_device"); ok { + vL := v.(*schema.Set).List() + for _, v := range vL { + bd := v.(map[string]interface{}) + if blockDeviceTags, ok := bd["tags"].(map[string]interface{}); ok && len(blockDeviceTags) > 0 { + return true + } + } + } + + return false +} + func getCreditSpecifications(conn *ec2.EC2, instanceId string) ([]map[string]interface{}, error) { var creditSpecifications []map[string]interface{} creditSpecification := make(map[string]interface{}) diff --git a/aws/resource_aws_instance_test.go b/aws/resource_aws_instance_test.go index 1b38cd0e5cc..4fc3687923c 100644 --- a/aws/resource_aws_instance_test.go +++ b/aws/resource_aws_instance_test.go @@ -334,7 +334,7 @@ func TestAccAWSInstance_EbsBlockDevice_InvalidIopsForVolumeType(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccCheckInstanceConfigEBSBlockDeviceInvalidIops, + Config: testAccInstanceConfigEBSBlockDeviceInvalidIops, ExpectError: regexp.MustCompile(`error creating resource: iops attribute not supported for ebs_block_device with volume_type gp2`), }, }, @@ -348,7 +348,7 @@ func TestAccAWSInstance_EbsBlockDevice_InvalidThroughputForVolumeType(t *testing CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccCheckInstanceConfigEBSBlockDeviceInvalidThroughput, + Config: testAccInstanceConfigEBSBlockDeviceInvalidThroughput, ExpectError: regexp.MustCompile(`error creating resource: throughput attribute not supported for ebs_block_device with volume_type gp2`), }, }, @@ -1042,7 +1042,7 @@ func TestAccAWSInstance_tags(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccCheckInstanceConfigTags(), + Config: testAccInstanceConfigTags(), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), @@ -1055,7 +1055,7 @@ func TestAccAWSInstance_tags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccCheckInstanceConfigTagsUpdate(), + Config: testAccInstanceConfigTagsUpdate(), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), @@ -1066,7 +1066,7 @@ func TestAccAWSInstance_tags(t *testing.T) { }) } -func TestAccAWSInstance_volumeTags(t *testing.T) { +func TestAccAWSInstance_blockDeviceTags_volumeTags(t *testing.T) { var v ec2.Instance resourceName := "aws_instance.test" @@ -1076,7 +1076,7 @@ func TestAccAWSInstance_volumeTags(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccCheckInstanceConfigNoVolumeTags(), + Config: testAccInstanceConfigBlockDeviceTagsNoVolumeTags(), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resourceName, &v), resource.TestCheckNoResourceAttr(resourceName, "volume_tags"), @@ -1089,7 +1089,7 @@ func TestAccAWSInstance_volumeTags(t *testing.T) { ImportStateVerifyIgnore: []string{"ephemeral_block_device"}, }, { - Config: testAccCheckInstanceConfigWithVolumeTags(), + Config: testAccInstanceConfigBlockDeviceTagsVolumeTags(), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "volume_tags.%", "1"), @@ -1097,7 +1097,7 @@ func TestAccAWSInstance_volumeTags(t *testing.T) { ), }, { - Config: testAccCheckInstanceConfigWithVolumeTagsUpdate(), + Config: testAccInstanceConfigBlockDeviceTagsVolumeTagsUpdate(), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "volume_tags.%", "2"), @@ -1106,7 +1106,7 @@ func TestAccAWSInstance_volumeTags(t *testing.T) { ), }, { - Config: testAccCheckInstanceConfigNoVolumeTags(), + Config: testAccInstanceConfigBlockDeviceTagsNoVolumeTags(), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resourceName, &v), resource.TestCheckNoResourceAttr(resourceName, "volume_tags"), @@ -1116,9 +1116,11 @@ func TestAccAWSInstance_volumeTags(t *testing.T) { }) } -func TestAccAWSInstance_volumeTagsComputed(t *testing.T) { +func TestAccAWSInstance_blockDeviceTags_withAttachedVolume(t *testing.T) { var v ec2.Instance resourceName := "aws_instance.test" + ebsVolumeName := "aws_ebs_volume.test" + rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -1126,9 +1128,31 @@ func TestAccAWSInstance_volumeTagsComputed(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccCheckInstanceConfigWithAttachedVolume(), + Config: testAccInstanceConfigBlockDeviceTagsAttachedVolumeWithTags(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists(resourceName, &v), + resource.TestCheckResourceAttr(ebsVolumeName, "tags.%", "2"), + resource.TestCheckResourceAttr(ebsVolumeName, "tags.Name", rName), + resource.TestCheckResourceAttr(ebsVolumeName, "tags.Factum", "PerAsperaAdAstra"), + ), + }, + { + //https://github.com/hashicorp/terraform-provider-aws/issues/17074 + Config: testAccInstanceConfigBlockDeviceTagsAttachedVolumeWithTags(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists(resourceName, &v), + resource.TestCheckResourceAttr(ebsVolumeName, "tags.%", "2"), + resource.TestCheckResourceAttr(ebsVolumeName, "tags.Name", rName), + resource.TestCheckResourceAttr(ebsVolumeName, "tags.Factum", "PerAsperaAdAstra"), + ), + }, + { + Config: testAccInstanceConfigBlockDeviceTagsAttachedVolumeWithTagsUpdate(rName), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resourceName, &v), + resource.TestCheckResourceAttr(ebsVolumeName, "tags.%", "2"), + resource.TestCheckResourceAttr(ebsVolumeName, "tags.Name", rName), + resource.TestCheckResourceAttr(ebsVolumeName, "tags.Factum", "VincitQuiSeVincit"), ), }, { @@ -1140,6 +1164,62 @@ func TestAccAWSInstance_volumeTagsComputed(t *testing.T) { }) } +func TestAccAWSInstance_blockDeviceTags_ebsAndRoot(t *testing.T) { + var v ec2.Instance + resourceName := "aws_instance.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceConfigBlockDeviceTagsRootTagsConflict(), + ExpectError: regexp.MustCompile(`"root_block_device\.0\.tags": conflicts with volume_tags`), + }, + { + Config: testAccInstanceConfigBlockDeviceTagsEBSTagsConflict(), + ExpectError: regexp.MustCompile(`"ebs_block_device\.0\.tags": conflicts with volume_tags`), + }, + { + Config: testAccInstanceConfigBlockDeviceTagsEBSTags(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "root_block_device.0.tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "ebs_block_device.0.tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "ebs_block_device.0.tags.Name", rName), + resource.TestCheckResourceAttr(resourceName, "ebs_block_device.1.tags.%", "0"), + ), + }, + { + Config: testAccInstanceConfigBlockDeviceTagsEBSAndRootTags(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "root_block_device.0.tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "root_block_device.0.tags.Name", rName), + resource.TestCheckResourceAttr(resourceName, "root_block_device.0.tags.Purpose", "test"), + ), + }, + { + Config: testAccInstanceConfigBlockDeviceTagsEBSAndRootTagsUpdate(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "root_block_device.0.tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "root_block_device.0.tags.Name", rName), + resource.TestCheckResourceAttr(resourceName, "root_block_device.0.tags.Env", "dev"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"ephemeral_block_device"}, + }, + }, + }) +} + func TestAccAWSInstance_instanceProfileChange(t *testing.T) { var v ec2.Instance resourceName := "aws_instance.test" @@ -3434,8 +3514,9 @@ func testAccAvailableAZsWavelengthZonesDefaultExcludeConfig() string { } func testAccInstanceConfigInDefaultVpcBySgName(rName string) string { - return testAccAvailableAZsNoOptInDefaultExcludeConfig() + - testAccLatestAmazonLinuxHvmEbsAmiConfig() + + return composeConfig( + testAccAvailableAZsNoOptInDefaultExcludeConfig(), + testAccLatestAmazonLinuxHvmEbsAmiConfig(), fmt.Sprintf(` data "aws_vpc" "default" { default = true @@ -3453,12 +3534,13 @@ resource "aws_instance" "test" { security_groups = [aws_security_group.test.name] availability_zone = data.aws_availability_zones.available.names[0] } -`, rName) +`, rName)) } func testAccInstanceConfigInDefaultVpcBySgId(rName string) string { - return testAccAvailableAZsNoOptInDefaultExcludeConfig() + - testAccLatestAmazonLinuxHvmEbsAmiConfig() + + return composeConfig( + testAccAvailableAZsNoOptInDefaultExcludeConfig(), + testAccLatestAmazonLinuxHvmEbsAmiConfig(), fmt.Sprintf(` data "aws_vpc" "default" { default = true @@ -3476,7 +3558,7 @@ resource "aws_instance" "test" { vpc_security_group_ids = [aws_security_group.test.id] availability_zone = data.aws_availability_zones.available.names[0] } -`, rName) +`, rName)) } func testAccInstanceConfigInEc2Classic() string { @@ -3507,7 +3589,10 @@ resource "aws_instance" "test" { } func testAccInstanceConfigAtLeastOneOtherEbsVolume(rName string) string { - return composeConfig(testAccLatestAmazonLinuxHvmInstanceStoreAmiConfig(), testAccAwsInstanceVpcConfig(rName, false), fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmInstanceStoreAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + fmt.Sprintf(` # Ensure that there is at least 1 EBS volume in the current region. # See https://github.com/hashicorp/terraform/issues/1249. resource "aws_ebs_volume" "test" { @@ -3537,7 +3622,10 @@ resource "aws_instance" "test" { } func testAccInstanceConfigWithUserDataBase64(rName string) string { - return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), testAccAwsInstanceVpcConfig(rName, false), ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id subnet_id = aws_subnet.test.id @@ -3549,7 +3637,10 @@ resource "aws_instance" "test" { } func testAccInstanceConfigWithSmallInstanceType(rName string) string { - return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), testAccAwsInstanceVpcConfig(rName, false), ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id subnet_id = aws_subnet.test.id @@ -3564,7 +3655,10 @@ resource "aws_instance" "test" { } func testAccInstanceConfigUpdateInstanceType(rName string) string { - return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), testAccAwsInstanceVpcConfig(rName, false), ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id subnet_id = aws_subnet.test.id @@ -3622,7 +3716,8 @@ resource "aws_instance" "test" { } func testAccInstanceConfigNoAMIEphemeralDevices() string { - return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), testAccAvailableEc2InstanceTypeForRegion("t3.micro", "t2.micro"), ` resource "aws_instance" "test" { @@ -3665,8 +3760,7 @@ func testAccAwsEc2InstanceRootBlockDeviceWithIOPS(size, delete, volumeType, iops if iops == "" { iops = "null" } - return composeConfig(testAccAwsEc2InstanceAmiWithEbsRootVolume, - fmt.Sprintf(` + return composeConfig(testAccAwsEc2InstanceAmiWithEbsRootVolume, fmt.Sprintf(` resource "aws_instance" "test" { ami = data.aws_ami.ami.id @@ -3686,8 +3780,7 @@ func testAccAwsEc2InstanceRootBlockDeviceWithThroughput(size, delete, volumeType if throughput == "" { throughput = "null" } - return composeConfig(testAccAwsEc2InstanceAmiWithEbsRootVolume, - fmt.Sprintf(` + return composeConfig(testAccAwsEc2InstanceAmiWithEbsRootVolume, fmt.Sprintf(` resource "aws_instance" "test" { ami = data.aws_ami.ami.id @@ -3783,39 +3876,51 @@ resource "aws_instance" "test" { } func testAccInstanceConfigSourceDestEnable(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.small" subnet_id = aws_subnet.test.id } -` +`) } func testAccInstanceConfigSourceDestDisable(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.small" subnet_id = aws_subnet.test.id source_dest_check = false } -` +`) } func testAccInstanceConfigDisableAPITermination(rName string, val bool) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + fmt.Sprintf(` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.small" subnet_id = aws_subnet.test.id disable_api_termination = %[1]t } -`, val) +`, val)) } func testAccEc2InstanceConfigDedicatedInstance(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id @@ -3827,13 +3932,11 @@ resource "aws_instance" "test" { # pre-encoded base64 data user_data = "3dc39dda39be1205215e776bad998da361a5955d" } -` +`) } func testAccInstanceConfigOutpost() string { - return composeConfig( - testAccLatestAmazonLinuxHvmEbsAmiConfig(), - ` + return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), ` data "aws_outposts_outposts" "test" {} data "aws_outposts_outpost" "test" { @@ -3869,7 +3972,10 @@ resource "aws_instance" "test" { } func testAccInstanceConfigPlacementGroup(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + fmt.Sprintf(` resource "aws_placement_group" "test" { name = %[1]q strategy = "cluster" @@ -3886,11 +3992,14 @@ resource "aws_instance" "test" { # pre-encoded base64 data user_data = "3dc39dda39be1205215e776bad998da361a5955d" } -`, rName) +`, rName)) } func testAccInstanceConfigIpv6ErrorConfig(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcIpv6Config(rName) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcIpv6Config(rName), + fmt.Sprintf(` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" @@ -3902,11 +4011,14 @@ resource "aws_instance" "test" { Name = %[1]q } } -`, rName) +`, rName)) } func testAccInstanceConfigIpv6Support(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcIpv6Config(rName) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcIpv6Config(rName), + fmt.Sprintf(` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" @@ -3917,11 +4029,14 @@ resource "aws_instance" "test" { Name = %[1]q } } -`, rName) +`, rName)) } func testAccInstanceConfigIpv6SupportWithIpv4(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcIpv6Config(rName) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcIpv6Config(rName), + fmt.Sprintf(` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" @@ -3933,10 +4048,10 @@ resource "aws_instance" "test" { Name = %[1]q } } -`, rName) +`, rName)) } -func testAccCheckInstanceConfigTags() string { +func testAccInstanceConfigTags() string { return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id @@ -3978,7 +4093,10 @@ resource "aws_instance" "test" { } func testAccInstanceConfigRootBlockDeviceKmsKeyArn(rName string) string { - return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), testAccAwsInstanceVpcConfig(rName, false), ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_kms_key" "test" { deletion_window_in_days = 7 } @@ -3997,30 +4115,59 @@ resource "aws_instance" "test" { `) } -func testAccCheckInstanceConfigWithAttachedVolume() string { - return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), ` +func testAccInstanceConfigBlockDeviceTagsAttachedVolumeWithTags(rName string) string { + // https://github.com/hashicorp/terraform-provider-aws/issues/17074 + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAvailableAZsNoOptInConfig(), + testAccAvailableEc2InstanceTypeForAvailabilityZone("data.aws_availability_zones.available.names[0]", "t3.micro", "t2.micro"), + fmt.Sprintf(` resource "aws_instance" "test" { - ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id - instance_type = "t2.medium" + ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id + instance_type = data.aws_ec2_instance_type_offering.available.instance_type + availability_zone = data.aws_availability_zones.available.names[0] +} - root_block_device { - delete_on_termination = true - volume_size = "10" - volume_type = "standard" - } +resource "aws_ebs_volume" "test" { + availability_zone = aws_instance.test.availability_zone + size = "10" + type = "gp2" tags = { - Name = "test-terraform" + Name = %[1]q + Factum = "PerAsperaAdAstra" } } +resource "aws_volume_attachment" "test" { + device_name = "/dev/xvdg" + volume_id = aws_ebs_volume.test.id + instance_id = aws_instance.test.id +} +`, rName)) +} + +func testAccInstanceConfigBlockDeviceTagsAttachedVolumeWithTagsUpdate(rName string) string { + // https://github.com/hashicorp/terraform-provider-aws/issues/17074 + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAvailableAZsNoOptInConfig(), + testAccAvailableEc2InstanceTypeForAvailabilityZone("data.aws_availability_zones.available.names[0]", "t3.micro", "t2.micro"), + fmt.Sprintf(` +resource "aws_instance" "test" { + ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id + instance_type = data.aws_ec2_instance_type_offering.available.instance_type + availability_zone = data.aws_availability_zones.available.names[0] +} + resource "aws_ebs_volume" "test" { availability_zone = aws_instance.test.availability_zone size = "10" type = "gp2" tags = { - Name = "test-terraform" + Name = %[1]q + Factum = "VincitQuiSeVincit" } } @@ -4029,10 +4176,66 @@ resource "aws_volume_attachment" "test" { volume_id = aws_ebs_volume.test.id instance_id = aws_instance.test.id } +`, rName)) +} + +func testAccInstanceConfigBlockDeviceTagsRootTagsConflict() string { + return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), ` +resource "aws_instance" "test" { + ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id + + instance_type = "t2.medium" + + root_block_device { + volume_type = "gp2" + volume_size = 11 + + tags = { + Name = "root-tag" + } + } + + ebs_block_device { + device_name = "/dev/sdb" + volume_size = 9 + } + + volume_tags = { + Name = "volume-tags" + } +} `) } -func testAccCheckInstanceConfigNoVolumeTags() string { +func testAccInstanceConfigBlockDeviceTagsEBSTagsConflict() string { + return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), ` +resource "aws_instance" "test" { + ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id + + instance_type = "t2.medium" + + root_block_device { + volume_type = "gp2" + volume_size = 11 + } + + ebs_block_device { + device_name = "/dev/sdb" + volume_size = 9 + + tags = { + Name = "ebs-volume" + } + } + + volume_tags = { + Name = "volume-tags" + } +} +`) +} + +func testAccInstanceConfigBlockDeviceTagsNoVolumeTags() string { return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id @@ -4070,7 +4273,116 @@ resource "aws_instance" "test" { `) } -var testAccCheckInstanceConfigEBSBlockDeviceInvalidIops = composeConfig(testAccAwsEc2InstanceAmiWithEbsRootVolume, ` +func testAccInstanceConfigBlockDeviceTagsEBSTags(rName string) string { + return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), fmt.Sprintf(` +resource "aws_instance" "test" { + ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id + + instance_type = "t2.medium" + + root_block_device { + volume_type = "gp2" + } + + ebs_block_device { + device_name = "/dev/sdb" + volume_size = 1 + + tags = { + Name = %[1]q + } + } + + ebs_block_device { + device_name = "/dev/sdc" + volume_size = 1 + } + + ephemeral_block_device { + device_name = "/dev/sde" + virtual_name = "ephemeral0" + } +} +`, rName)) +} + +func testAccInstanceConfigBlockDeviceTagsEBSAndRootTags(rName string) string { + return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), fmt.Sprintf(` +resource "aws_instance" "test" { + ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id + + instance_type = "t2.medium" + + root_block_device { + volume_type = "gp2" + + tags = { + Name = %[1]q + Purpose = "test" + } + } + + ebs_block_device { + device_name = "/dev/sdb" + volume_size = 1 + + tags = { + Name = %[1]q + } + } + + ebs_block_device { + device_name = "/dev/sdc" + volume_size = 1 + } + + ephemeral_block_device { + device_name = "/dev/sde" + virtual_name = "ephemeral0" + } +} +`, rName)) +} + +func testAccInstanceConfigBlockDeviceTagsEBSAndRootTagsUpdate(rName string) string { + return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), fmt.Sprintf(` +resource "aws_instance" "test" { + ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id + + instance_type = "t2.medium" + + root_block_device { + volume_type = "gp2" + + tags = { + Name = %[1]q + Env = "dev" + } + } + + ebs_block_device { + device_name = "/dev/sdb" + volume_size = 1 + + tags = { + Name = %[1]q + } + } + + ebs_block_device { + device_name = "/dev/sdc" + volume_size = 1 + } + + ephemeral_block_device { + device_name = "/dev/sde" + virtual_name = "ephemeral0" + } +} +`, rName)) +} + +var testAccInstanceConfigEBSBlockDeviceInvalidIops = composeConfig(testAccAwsEc2InstanceAmiWithEbsRootVolume, ` resource "aws_instance" "test" { ami = data.aws_ami.ami.id @@ -4085,7 +4397,7 @@ resource "aws_instance" "test" { } `) -var testAccCheckInstanceConfigEBSBlockDeviceInvalidThroughput = composeConfig(testAccAwsEc2InstanceAmiWithEbsRootVolume, ` +var testAccInstanceConfigEBSBlockDeviceInvalidThroughput = composeConfig(testAccAwsEc2InstanceAmiWithEbsRootVolume, ` resource "aws_instance" "test" { ami = data.aws_ami.ami.id @@ -4100,7 +4412,7 @@ resource "aws_instance" "test" { } `) -func testAccCheckInstanceConfigWithVolumeTags() string { +func testAccInstanceConfigBlockDeviceTagsVolumeTags() string { return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id @@ -4142,7 +4454,7 @@ resource "aws_instance" "test" { `) } -func testAccCheckInstanceConfigWithVolumeTagsUpdate() string { +func testAccInstanceConfigBlockDeviceTagsVolumeTagsUpdate() string { return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id @@ -4185,7 +4497,7 @@ resource "aws_instance" "test" { `) } -func testAccCheckInstanceConfigTagsUpdate() string { +func testAccInstanceConfigTagsUpdate() string { return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id @@ -4199,7 +4511,7 @@ resource "aws_instance" "test" { } func testAccInstanceConfigWithoutInstanceProfile(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + fmt.Sprintf(` + return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), fmt.Sprintf(` resource "aws_iam_role" "test" { name = %[1]q @@ -4232,11 +4544,11 @@ resource "aws_instance" "test" { Name = %[1]q } } -`, rName) +`, rName)) } func testAccInstanceConfigWithInstanceProfile(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + fmt.Sprintf(` + return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), fmt.Sprintf(` resource "aws_iam_role" "test" { name = %[1]q @@ -4275,33 +4587,42 @@ resource "aws_instance" "test" { Name = %[1]q } } -`, rName) +`, rName)) } func testAccInstanceConfigPrivateIP(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" subnet_id = aws_subnet.test.id private_ip = "10.1.1.42" } -` +`) } func testAccInstanceConfigEmptyPrivateIP(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" subnet_id = aws_subnet.test.id private_ip = "" } -` +`) } func testAccInstanceConfigAssociatePublicIPAndPrivateIP(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" @@ -4309,14 +4630,15 @@ resource "aws_instance" "test" { associate_public_ip_address = true private_ip = "10.1.1.42" } -` +`) } func testAccInstanceNetworkInstanceSecurityGroups(rName string) string { return composeConfig( testAccLatestAmazonLinuxHvmEbsAmiConfig(), testAccAwsInstanceVpcConfig(rName, false), - testAccAwsInstanceVpcSecurityGroupConfig(rName), ` + testAccAwsInstanceVpcSecurityGroupConfig(rName), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" @@ -4338,7 +4660,8 @@ func testAccInstanceNetworkInstanceVPCSecurityGroupIDs(rName string) string { return composeConfig( testAccLatestAmazonLinuxHvmEbsAmiConfig(), testAccAwsInstanceVpcConfig(rName, false), - testAccAwsInstanceVpcSecurityGroupConfig(rName), ` + testAccAwsInstanceVpcSecurityGroupConfig(rName), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" @@ -4359,7 +4682,8 @@ func testAccInstanceNetworkInstanceVPCRemoveSecurityGroupIDs(rName string) strin return composeConfig( testAccLatestAmazonLinuxHvmEbsAmiConfig(), testAccAwsInstanceVpcConfig(rName, false), - testAccAwsInstanceVpcSecurityGroupConfig(rName), ` + testAccAwsInstanceVpcSecurityGroupConfig(rName), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" @@ -4377,7 +4701,7 @@ resource "aws_eip" "test" { } func testAccInstanceConfigKeyPair(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + fmt.Sprintf(` + return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), fmt.Sprintf(` resource "aws_key_pair" "test" { key_name = %[1]q public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD3F6tyPEFEzV0LX3X8BsXdMsQz1x2cEikKDEY0aIj41qgxMCP/iteneqXSIFZBp5vizPvaoIR3Um9xK7PGoW8giupGn+EPuxIA4cDM4vzOqOkiMPhz5XK0whEjkVzTo4+S0puvDZuwIsdiW9mxhJc7tgBNL0cYlWSYVkz4G/fslNfRPW5mYAM49f4fhtxPb5ok4Q2Lg9dPKVHO/Bgeu5woMc7RY0p1ej6D4CKFE6lymSDJpW0YHX/wqE9+cfEauh7xZcG0q9t2ta6F6fmX0agvpFyZo8aFbXeUBr7osSCJNgvavWbM/06niWrOvYX2xwWdhXmXSrbX8ZbabVohBK41 phodgson@thoughtworks.com" @@ -4392,11 +4716,11 @@ resource "aws_instance" "test" { Name = %[1]q } } -`, rName) +`, rName)) } func testAccInstanceConfigRootBlockDeviceMismatch(rName string) string { - return testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig(testAccAwsInstanceVpcConfig(rName, false), ` resource "aws_instance" "test" { # This is an AMI in UsWest2 with RootDeviceName: "/dev/sda1"; actual root: "/dev/sda" ami = "ami-ef5b69df" @@ -4409,31 +4733,40 @@ resource "aws_instance" "test" { volume_size = 13 } } -` //lintignore:AWSAT002 +`) //lintignore:AWSAT002 } func testAccInstanceConfigForceNewAndTagsDrift(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.nano" subnet_id = aws_subnet.test.id } -` +`) } func testAccInstanceConfigForceNewAndTagsDrift_Update(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" subnet_id = aws_subnet.test.id } -` +`) } func testAccInstanceConfigPrimaryNetworkInterface(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + fmt.Sprintf(` resource "aws_network_interface" "test" { subnet_id = aws_subnet.test.id private_ips = ["10.1.1.42"] @@ -4452,11 +4785,14 @@ resource "aws_instance" "test" { device_index = 0 } } -`, rName) +`, rName)) } func testAccInstanceConfigPrimaryNetworkInterfaceSourceDestCheck(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + fmt.Sprintf(` resource "aws_network_interface" "test" { subnet_id = aws_subnet.test.id private_ips = ["10.1.1.42"] @@ -4476,11 +4812,14 @@ resource "aws_instance" "test" { device_index = 0 } } -`, rName) +`, rName)) } func testAccInstanceConfigAddSecondaryNetworkInterfaceBefore(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + fmt.Sprintf(` resource "aws_network_interface" "primary" { subnet_id = aws_subnet.test.id private_ips = ["10.1.1.42"] @@ -4508,11 +4847,14 @@ resource "aws_instance" "test" { device_index = 0 } } -`, rName) +`, rName)) } func testAccInstanceConfigAddSecondaryNetworkInterfaceAfter(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + fmt.Sprintf(` resource "aws_network_interface" "primary" { subnet_id = aws_subnet.test.id private_ips = ["10.1.1.42"] @@ -4546,11 +4888,14 @@ resource "aws_instance" "test" { device_index = 0 } } -`, rName) +`, rName)) } func testAccInstanceConfigAddSecurityGroupBefore(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + fmt.Sprintf(` resource "aws_subnet" "test2" { cidr_block = "10.1.2.0/24" vpc_id = aws_vpc.test.id @@ -4603,11 +4948,14 @@ resource "aws_network_interface" "test" { Name = %[1]q } } -`, rName) +`, rName)) } func testAccInstanceConfigAddSecurityGroupAfter(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + fmt.Sprintf(` resource "aws_subnet" "test2" { cidr_block = "10.1.2.0/24" vpc_id = aws_vpc.test.id @@ -4661,11 +5009,14 @@ resource "aws_network_interface" "test" { Name = %[1]q } } -`, rName) +`, rName)) } func testAccInstanceConfigPublicAndPrivateSecondaryIPs(rName string, isPublic bool) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + fmt.Sprintf(` resource "aws_security_group" "test" { vpc_id = aws_vpc.test.id description = "%[1]s" @@ -4689,11 +5040,14 @@ resource "aws_instance" "test" { Name = %[1]q } } -`, rName, isPublic) +`, rName, isPublic)) } func testAccInstanceConfigPrivateIPAndSecondaryIPs(rName, privateIP, secondaryIPs string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + fmt.Sprintf(` resource "aws_security_group" "test" { vpc_id = aws_vpc.test.id description = "%[1]s" @@ -4716,11 +5070,14 @@ resource "aws_instance" "test" { Name = %[1]q } } -`, rName, privateIP, secondaryIPs) +`, rName, privateIP, secondaryIPs)) } func testAccInstanceConfig_associatePublic_defaultPrivate(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + fmt.Sprintf(` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" @@ -4730,11 +5087,14 @@ resource "aws_instance" "test" { Name = %[1]q } } -`, rName) +`, rName)) } func testAccInstanceConfig_associatePublic_defaultPublic(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, true) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, true), + fmt.Sprintf(` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" @@ -4744,11 +5104,14 @@ resource "aws_instance" "test" { Name = %[1]q } } -`, rName) +`, rName)) } func testAccInstanceConfig_associatePublic_explicitPublic(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, true) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, true), + fmt.Sprintf(` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" @@ -4759,11 +5122,14 @@ resource "aws_instance" "test" { Name = %[1]q } } -`, rName) +`, rName)) } func testAccInstanceConfig_associatePublic_explicitPrivate(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, true) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, true), + fmt.Sprintf(` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" @@ -4774,11 +5140,14 @@ resource "aws_instance" "test" { Name = %[1]q } } -`, rName) +`, rName)) } func testAccInstanceConfig_associatePublic_overridePublic(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + fmt.Sprintf(` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" @@ -4789,11 +5158,14 @@ resource "aws_instance" "test" { Name = %[1]q } } -`, rName) +`, rName)) } func testAccInstanceConfig_associatePublic_overridePrivate(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, true) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, true), + fmt.Sprintf(` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" @@ -4804,11 +5176,11 @@ resource "aws_instance" "test" { Name = %[1]q } } -`, rName) +`, rName)) } func testAccInstanceConfig_getPasswordData(rName string, val bool) string { - return testAccLatestWindowsServer2016CoreAmiConfig() + fmt.Sprintf(` + return composeConfig(testAccLatestWindowsServer2016CoreAmiConfig(), fmt.Sprintf(` resource "aws_key_pair" "test" { key_name = %[1]q public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAq6U3HQYC4g8WzU147gZZ7CKQH8TgYn3chZGRPxaGmHW1RUwsyEs0nmombmIhwxudhJ4ehjqXsDLoQpd6+c7BuLgTMvbv8LgE9LX53vnljFe1dsObsr/fYLvpU9LTlo8HgHAqO5ibNdrAUvV31ronzCZhms/Gyfdaue88Fd0/YnsZVGeOZPayRkdOHSpqme2CBrpa8myBeL1CWl0LkDG4+YCURjbaelfyZlIApLYKy3FcCan9XQFKaL32MJZwCgzfOvWIMtYcU8QtXMgnA3/I3gXk8YDUJv5P4lj0s/PJXuTM8DygVAUtebNwPuinS7wwonm5FXcWMuVGsVpG5K7FGQ== tf-acc-winpasswordtest" @@ -4821,11 +5193,14 @@ resource "aws_instance" "test" { get_password_data = %[2]t } -`, rName, val) +`, rName, val)) } func testAccInstanceConfig_CreditSpecification_Empty_NonBurstable(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "m5.large" @@ -4833,41 +5208,53 @@ resource "aws_instance" "test" { credit_specification {} } -` +`) } func testAccInstanceConfig_CreditSpecification_Unspecified_NonBurstable(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "m5.large" subnet_id = aws_subnet.test.id } -` +`) } func testAccInstanceConfig_creditSpecification_unspecified(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" subnet_id = aws_subnet.test.id } -` +`) } func testAccInstanceConfig_creditSpecification_unspecified_t3(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t3.micro" subnet_id = aws_subnet.test.id } -` +`) } func testAccInstanceConfig_creditSpecification_standardCpuCredits(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" @@ -4877,11 +5264,14 @@ resource "aws_instance" "test" { cpu_credits = "standard" } } -` +`) } func testAccInstanceConfig_creditSpecification_standardCpuCredits_t3(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t3.micro" @@ -4891,11 +5281,14 @@ resource "aws_instance" "test" { cpu_credits = "standard" } } -` +`) } func testAccInstanceConfig_creditSpecification_unlimitedCpuCredits(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" @@ -4905,11 +5298,14 @@ resource "aws_instance" "test" { cpu_credits = "unlimited" } } -` +`) } func testAccInstanceConfig_creditSpecification_unlimitedCpuCredits_t3(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t3.micro" @@ -4919,11 +5315,14 @@ resource "aws_instance" "test" { cpu_credits = "unlimited" } } -` +`) } func testAccInstanceConfig_creditSpecification_isNotAppliedToNonBurstable(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.small" @@ -4933,11 +5332,14 @@ resource "aws_instance" "test" { cpu_credits = "standard" } } -` +`) } func testAccInstanceConfig_creditSpecification_unknownCpuCredits(rName, instanceType string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + fmt.Sprintf(` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + fmt.Sprintf(` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = %[1]q @@ -4945,28 +5347,34 @@ resource "aws_instance" "test" { credit_specification {} } -`, instanceType) +`, instanceType)) } func testAccInstanceConfig_UserData_Unspecified(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" subnet_id = aws_subnet.test.id } -` +`) } func testAccInstanceConfig_UserData_EmptyString(rName string) string { - return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + ` + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + ` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id instance_type = "t2.micro" subnet_id = aws_subnet.test.id user_data = "" } -` +`) } // testAccLatestAmazonLinuxHvmEbsAmiConfig returns the configuration for a data source that @@ -5186,9 +5594,7 @@ resource "aws_subnet" "test" { } func testAccInstanceConfigHibernation(hibernation bool) string { - return composeConfig( - testAccLatestAmazonLinuxHvmEbsAmiConfig(), - fmt.Sprintf(` + return composeConfig(testAccLatestAmazonLinuxHvmEbsAmiConfig(), fmt.Sprintf(` resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" diff --git a/aws/tags.go b/aws/tags.go index 8abb4db0c55..cf9ed21dfe9 100644 --- a/aws/tags.go +++ b/aws/tags.go @@ -35,6 +35,15 @@ func tagsSchemaForceNew() *schema.Schema { } } +func tagsSchemaConflictsWith(conflictsWith []string) *schema.Schema { + return &schema.Schema{ + ConflictsWith: conflictsWith, + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + } +} + // ec2TagsFromTagDescriptions returns the tags from the given tag descriptions. // No attempt is made to remove duplicates. func ec2TagsFromTagDescriptions(tds []*ec2.TagDescription) []*ec2.Tag { diff --git a/website/docs/r/instance.html.markdown b/website/docs/r/instance.html.markdown index f5520686d1b..bbe39b65ede 100644 --- a/website/docs/r/instance.html.markdown +++ b/website/docs/r/instance.html.markdown @@ -8,18 +8,13 @@ description: |- # Resource: aws_instance -Provides an EC2 instance resource. This allows instances to be created, updated, -and deleted. Instances also support [provisioning](https://www.terraform.io/docs/provisioners/index.html). +Provides an EC2 instance resource. This allows instances to be created, updated, and deleted. Instances also support [provisioning](https://www.terraform.io/docs/provisioners/index.html). ## Example Usage -```hcl -# Create a new instance of the latest Ubuntu 20.04 on an -# t3.micro node with an AWS Tag naming it "HelloWorld" -provider "aws" { - region = "us-west-2" -} +### Basic Example Using AMI Lookup +```hcl data "aws_ami" "ubuntu" { most_recent = true @@ -46,69 +41,100 @@ resource "aws_instance" "web" { } ``` +### Network and Credit Specification Example + +```hcl +resource "aws_vpc" "my_vpc" { + cidr_block = "172.16.0.0/16" + + tags = { + Name = "tf-example" + } +} + +resource "aws_subnet" "my_subnet" { + vpc_id = aws_vpc.my_vpc.id + cidr_block = "172.16.10.0/24" + availability_zone = "us-west-2a" + + tags = { + Name = "tf-example" + } +} + +resource "aws_network_interface" "foo" { + subnet_id = aws_subnet.my_subnet.id + private_ips = ["172.16.10.100"] + + tags = { + Name = "primary_network_interface" + } +} + +resource "aws_instance" "foo" { + ami = "ami-005e54dee72cc1d00" # us-west-2 + instance_type = "t2.micro" + + network_interface { + network_interface_id = aws_network_interface.foo.id + device_index = 0 + } + + credit_specification { + cpu_credits = "unlimited" + } +} +``` + ## Argument Reference The following arguments are supported: -* `ami` - (Required) The AMI to use for the instance. -* `availability_zone` - (Optional) The AZ to start the instance in. -* `placement_group` - (Optional) The Placement Group to start the instance in. -* `tenancy` - (Optional) The tenancy of the instance (if the instance is running in a VPC). An instance with a tenancy of dedicated runs on single-tenant hardware. The host tenancy is not supported for the import-instance command. -* `host_id` - (optional) The Id of a dedicated host that the instance will be assigned to. Use when an instance is to be launched on a specific dedicated host. -* `cpu_core_count` - (Optional) Sets the number of CPU cores for an instance. This option is - only supported on creation of instance type that support CPU Options - [CPU Cores and Threads Per CPU Core Per Instance Type](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-optimize-cpu.html#cpu-options-supported-instances-values) - specifying this option for unsupported instance types will return an error from the EC2 API. -* `cpu_threads_per_core` - (Optional - has no effect unless `cpu_core_count` is also set) If set to to 1, hyperthreading is disabled on the launched instance. Defaults to 2 if not set. See [Optimizing CPU Options](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-optimize-cpu.html) for more information. +* `ami` - (Required) AMI to use for the instance. +* `associate_public_ip_address` - (Optional) Whether to associate a public IP address with an instance in a VPC. +* `availability_zone` - (Optional) AZ to start the instance in. -> **NOTE:** Changing `cpu_core_count` and/or `cpu_threads_per_core` will cause the resource to be destroyed and re-created. -* `ebs_optimized` - (Optional) If true, the launched EC2 instance will be EBS-optimized. - Note that if this is not set on an instance type that is optimized by default then - this will show as disabled but if the instance type is optimized by default then - there is no need to set this and there is no effect to disabling it. - See the [EBS Optimized section](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSOptimized.html) of the AWS User Guide for more information. -* `disable_api_termination` - (Optional) If true, enables [EC2 Instance - Termination Protection](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/terminating-instances.html#Using_ChangingDisableAPITermination) -* `instance_initiated_shutdown_behavior` - (Optional) Shutdown behavior for the -instance. Amazon defaults this to `stop` for EBS-backed instances and -`terminate` for instance-store instances. Cannot be set on instance-store -instances. See [Shutdown Behavior](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/terminating-instances.html#Using_ChangingInstanceInitiatedShutdownBehavior) for more information. -* `instance_type` - (Required) The type of instance to start. Updates to this field will trigger a stop/start of the EC2 instance. -* `key_name` - (Optional) The key name of the Key Pair to use for the instance; which can be managed using [the `aws_key_pair` resource](key_pair.html). - +* `cpu_core_count` - (Optional) Sets the number of CPU cores for an instance. This option is only supported on creation of instance type that support CPU Options [CPU Cores and Threads Per CPU Core Per Instance Type](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-optimize-cpu.html#cpu-options-supported-instances-values) - specifying this option for unsupported instance types will return an error from the EC2 API. +* `cpu_threads_per_core` - (Optional - has no effect unless `cpu_core_count` is also set) If set to to 1, hyperthreading is disabled on the launched instance. Defaults to 2 if not set. See [Optimizing CPU Options](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-optimize-cpu.html) for more information. +* `credit_specification` - (Optional) Customize the credit specification of the instance. See [Credit Specification](#credit-specification) below for more details. +* `disable_api_termination` - (Optional) If true, enables [EC2 Instance Termination Protection](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/terminating-instances.html#Using_ChangingDisableAPITermination). +* `ebs_block_device` - (Optional) Additional EBS block devices to attach to the instance. Block device configurations only apply on resource creation. See [Block Devices](#block-devices) below for details on attributes and drift detection. +* `ebs_optimized` - (Optional) If true, the launched EC2 instance will be EBS-optimized. Note that if this is not set on an instance type that is optimized by default then this will show as disabled but if the instance type is optimized by default then there is no need to set this and there is no effect to disabling it. See the [EBS Optimized section](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSOptimized.html) of the AWS User Guide for more information. +* `enclave_options` - (Optional) Enable Nitro Enclaves on launched instances. See [Enclave Options](#enclave-options) below for more details. +* `ephemeral_block_device` - (Optional) Customize Ephemeral (also known as "Instance Store") volumes on the instance. See [Block Devices](#block-devices) below for details. * `get_password_data` - (Optional) If true, wait for password data to become available and retrieve it. Useful for getting the administrator password for instances running Microsoft Windows. The password data is exported to the `password_data` attribute. See [GetPasswordData](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_GetPasswordData.html) for more information. +* `hibernation` - (Optional) If true, the launched EC2 instance will support hibernation. +* `host_id` - (Optional) ID of a dedicated host that the instance will be assigned to. Use when an instance is to be launched on a specific dedicated host. +* `iam_instance_profile` - (Optional) IAM Instance Profile to launch the instance with. Specified as the name of the Instance Profile. Ensure your credentials have the correct permission to assign the instance profile according to the [EC2 documentation](http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2.html#roles-usingrole-ec2instance-permissions), notably `iam:PassRole`. +* `instance_initiated_shutdown_behavior` - (Optional) Shutdown behavior for the instance. Amazon defaults this to `stop` for EBS-backed instances and `terminate` for instance-store instances. Cannot be set on instance-store instances. See [Shutdown Behavior](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/terminating-instances.html#Using_ChangingInstanceInitiatedShutdownBehavior) for more information. +* `instance_type` - (Required) Type of instance to start. Updates to this field will trigger a stop/start of the EC2 instance. +* `ipv6_address_count`- (Optional) A number of IPv6 addresses to associate with the primary network interface. Amazon EC2 chooses the IPv6 addresses from the range of your subnet. +* `ipv6_addresses` - (Optional) Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface +* `key_name` - (Optional) Key name of the Key Pair to use for the instance; which can be managed using [the `aws_key_pair` resource](key_pair.html). +* `metadata_options` - (Optional) Customize the metadata options of the instance. See [Metadata Options](#metadata-options) below for more details. * `monitoring` - (Optional) If true, the launched EC2 instance will have detailed monitoring enabled. (Available since v0.6.0) +* `network_interface` - (Optional) Customize network interfaces to be attached at instance boot time. See [Network Interfaces](#network-interfaces) below for more details. +* `placement_group` - (Optional) Placement Group to start the instance in. +* `private_ip` - (Optional) Private IP address to associate with the instance in a VPC. +* `root_block_device` - (Optional) Customize details about the root block device of the instance. See [Block Devices](#block-devices) below for details. +* `secondary_private_ips` - (Optional) A list of secondary private IPv4 addresses to assign to the instance's primary network interface (eth0) in a VPC. Can only be assigned to the primary network interface (eth0) attached at instance creation, not a pre-existing network interface i.e. referenced in a `network_interface` block. Refer to the [Elastic network interfaces documentation](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-eni.html#AvailableIpPerENI) to see the maximum number of private IP addresses allowed per instance type. * `security_groups` - (Optional, EC2-Classic and default VPC only) A list of security group names (EC2-Classic) or IDs (default VPC) to associate with. +* `source_dest_check` - (Optional) Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs. Defaults true. +* `subnet_id` - (Optional) VPC Subnet ID to launch in. +* `tags` - (Optional) A map of tags to assign to the resource. Note that these tags apply to the instance and not block storage devices. +* `tenancy` - (Optional) Tenancy of the instance (if the instance is running in a VPC). An instance with a tenancy of dedicated runs on single-tenant hardware. The host tenancy is not supported for the import-instance command. +* `user_data` - (Optional) User data to provide when launching the instance. Do not pass gzip-compressed data via this argument; see `user_data_base64` instead. +* `user_data_base64` - (Optional) Can be used instead of `user_data` to pass base64-encoded binary data directly. Use this instead of `user_data` whenever the value is not a valid UTF-8 string. For example, gzip-encoded user data must be base64-encoded and passed via this argument to avoid corruption. + +~> **NOTE:** Do not use `volume_tags` if you plan to manage block device tags outside the `aws_instance` configuration, such as using `tags` in an [`aws_ebs_volume`](/docs/providers/aws/r/ebs_volume.html) resource attached via [`aws_volume_attachment`](/docs/providers/aws/r/volume_attachment.html). Doing so will result in resource cycling and inconsistent behavior. + +* `volume_tags` - (Optional) A map of tags to assign, at instance-creation time, to root and EBS volumes. -> **NOTE:** If you are creating Instances in a VPC, use `vpc_security_group_ids` instead. * `vpc_security_group_ids` - (Optional, VPC only) A list of security group IDs to associate with. -* `subnet_id` - (Optional) The VPC Subnet ID to launch in. -* `associate_public_ip_address` - (Optional) Associate a public ip address with an instance in a VPC. Boolean value. -* `private_ip` - (Optional) Private IP address to associate with the - instance in a VPC. -* `secondary_private_ips` - (Optional) A list of secondary private IPv4 addresses to assign to the instance's primary network interface (eth0) in a VPC. Can only be assigned to the primary network interface (eth0) attached at instance creation, not a pre-existing network interface i.e. referenced in a `network_interface` block. Refer to the [Elastic network interfaces documentation](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-eni.html#AvailableIpPerENI) to see the maximum number of private IP addresses allowed per instance type. -* `source_dest_check` - (Optional) Controls if traffic is routed to the instance when - the destination address does not match the instance. Used for NAT or VPNs. Defaults true. -* `user_data` - (Optional) The user data to provide when launching the instance. Do not pass gzip-compressed data via this argument; see `user_data_base64` instead. -* `user_data_base64` - (Optional) Can be used instead of `user_data` to pass base64-encoded binary data directly. Use this instead of `user_data` whenever the value is not a valid UTF-8 string. For example, gzip-encoded user data must be base64-encoded and passed via this argument to avoid corruption. -* `iam_instance_profile` - (Optional) The IAM Instance Profile to - launch the instance with. Specified as the name of the Instance Profile. Ensure your credentials have the correct permission to assign the instance profile according to the [EC2 documentation](http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2.html#roles-usingrole-ec2instance-permissions), notably `iam:PassRole`. -* `ipv6_address_count`- (Optional) A number of IPv6 addresses to associate with the primary network interface. Amazon EC2 chooses the IPv6 addresses from the range of your subnet. -* `ipv6_addresses` - (Optional) Specify one or more IPv6 addresses from the range of the subnet to associate with the primary network interface -* `tags` - (Optional) A map of tags to assign to the resource. -* `volume_tags` - (Optional) A map of tags to assign to the devices created by the instance at launch time. -* `root_block_device` - (Optional) Customize details about the root block - device of the instance. See [Block Devices](#block-devices) below for details. -* `ebs_block_device` - (Optional) Additional EBS block devices to attach to the - instance. Block device configurations only apply on resource creation. See [Block Devices](#block-devices) below for details on attributes and drift detection. -* `ephemeral_block_device` - (Optional) Customize Ephemeral (also known as - "Instance Store") volumes on the instance. See [Block Devices](#block-devices) below for details. -* `network_interface` - (Optional) Customize network interfaces to be attached at instance boot time. See [Network Interfaces](#network-interfaces) below for more details. -* `credit_specification` - (Optional) Customize the credit specification of the instance. See [Credit Specification](#credit-specification) below for more details. -* `hibernation` - (Optional) If true, the launched EC2 instance will support hibernation. -* `metadata_options` - (Optional) Customize the metadata options of the instance. See [Metadata Options](#metadata-options) below for more details. -* `enclave_options` - (Optional) Enable Nitro Enclaves on launched instances. See [Enclave Options](#enclave-options) below for more details. ### Timeouts @@ -118,98 +144,56 @@ The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/d * `update` - (Defaults to 10 mins) Used when stopping and starting the instance when necessary during update - e.g. when changing instance type * `delete` - (Defaults to 20 mins) Used when terminating the instance -### Block devices +### Credit Specification + +~> **NOTE:** Removing this configuration on existing instances will only stop managing it. It will not change the configuration back to the default for the instance type. + +Credit specification can be applied/modified to the EC2 Instance at any time. + +The `credit_specification` block supports the following: + +* `cpu_credits` - (Optional) Credit option for CPU usage. Valid values include `standard` or `unlimited`. T3 instances are launched as unlimited by default. T2 instances are launched as standard by default. + +### EBS, Ephemeral, and Root Block Devices Each of the `*_block_device` attributes control a portion of the AWS -Instance's "Block Device Mapping". It's a good idea to familiarize yourself with [AWS's Block Device -Mapping docs](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html) -to understand the implications of using these attributes. +Instance's "Block Device Mapping". It's a good idea to familiarize yourself with [AWS's Block Device Mapping docs](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html) to understand the implications of using these attributes. The `root_block_device` mapping supports the following: -* `volume_type` - (Optional) The type of volume. Can be `"standard"`, `"gp2"`, `"gp3"`, `"io1"`, `"io2"`, `"sc1"`, or `"st1"`. (Default: `"gp2"`). -* `volume_size` - (Optional) The size of the volume in gibibytes (GiB). -* `iops` - (Optional) The amount of provisioned - [IOPS](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-io-characteristics.html). Only valid for volume_type of `"io1"`, `"io2"` or `"gp3"`. -* `throughput` - (Optional) The throughput to provision for a volume in mebibytes per second (MiB/s). This is only valid for `volume_type` of `"gp3"`. -* `delete_on_termination` - (Optional) Whether the volume should be destroyed - on instance termination (Default: `true`). -* `encrypted` - (Optional) Enable volume encryption. (Default: `false`). Must be configured to perform drift detection. +* `delete_on_termination` - (Optional) Whether the volume should be destroyed on instance termination. Defaults to `true`. +* `encrypted` - (Optional) Whether to enable volume encryption. Defaults to `false`. Must be configured to perform drift detection. +* `iops` - (Optional) Amount of provisioned [IOPS](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-io-characteristics.html). Only valid for volume_type of `io1`, `io2` or `gp3`. * `kms_key_id` - (Optional) Amazon Resource Name (ARN) of the KMS Key to use when encrypting the volume. Must be configured to perform drift detection. +* `tags` - (Optional) A map of tags to assign to the device. +* `throughput` - (Optional) Throughput to provision for a volume in mebibytes per second (MiB/s). This is only valid for `volume_type` of `gp3`. +* `volume_size` - (Optional) Size of the volume in gibibytes (GiB). +* `volume_type` - (Optional) Type of volume. Valid values include `standard`, `gp2`, `gp3`, `io1`, `io2`, `sc1`, or `st1`. Defaults to `gp2`. -Modifying any of the `root_block_device` settings other than `volume_size` requires resource -replacement. +Modifying any of the `root_block_device` settings other than `volume_size` requires resource replacement. Each `ebs_block_device` supports the following: -* `device_name` - (Required) The name of the device to mount. -* `snapshot_id` - (Optional) The Snapshot ID to mount. -* `volume_type` - (Optional) The type of volume. Can be `"standard"`, `"gp2"`, `"gp3"`, `"io1"`, `"io2"`, `"sc1"`, or `"st1"`. (Default: `"gp2"`). -* `volume_size` - (Optional) The size of the volume in gibibytes (GiB). -* `iops` - (Optional) The amount of provisioned - [IOPS](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-io-characteristics.html). - Only valid for volume_type of `"io1"`, `"io2"` or `"gp3"`. -* `throughput` - (Optional) The throughput to provision for a volume in mebibytes per second (MiB/s). This is only valid for `volume_type` of `"gp3"`. -* `delete_on_termination` - (Optional) Whether the volume should be destroyed - on instance termination (Default: `true`). -* `encrypted` - (Optional) Enables [EBS - encryption](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html) - on the volume (Default: `false`). Cannot be used with `snapshot_id`. Must be configured to perform drift detection. +* `delete_on_termination` - (Optional) Whether the volume should be destroyed on instance termination. Defaults to `true`. +* `device_name` - (Required) Name of the device to mount. +* `encrypted` - (Optional) Enables [EBS encryption](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html) on the volume. Defaults to `false`. Cannot be used with `snapshot_id`. Must be configured to perform drift detection. +* `iops` - (Optional) Amount of provisioned [IOPS](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-io-characteristics.html). Only valid for volume_type of `io1`, `io2` or `gp3`. * `kms_key_id` - (Optional) Amazon Resource Name (ARN) of the KMS Key to use when encrypting the volume. Must be configured to perform drift detection. +* `snapshot_id` - (Optional) Snapshot ID to mount. +* `tags` - (Optional) A map of tags to assign to the device. +* `throughput` - (Optional) Throughput to provision for a volume in mebibytes per second (MiB/s). This is only valid for `volume_type` of `gp3`. +* `volume_size` - (Optional) Size of the volume in gibibytes (GiB). +* `volume_type` - (Optional) Type of volume. Valid values include `standard`, `gp2`, `gp3`, `io1`, `io2`, `sc1`, or `st1`. Defaults to `gp2`. ~> **NOTE:** Currently, changes to the `ebs_block_device` configuration of _existing_ resources cannot be automatically detected by Terraform. To manage changes and attachments of an EBS block to an instance, use the `aws_ebs_volume` and `aws_volume_attachment` resources instead. If you use `ebs_block_device` on an `aws_instance`, Terraform will assume management over the full set of non-root EBS block devices for the instance, treating additional block devices as drift. For this reason, `ebs_block_device` cannot be mixed with external `aws_ebs_volume` and `aws_volume_attachment` resources for a given instance. Each `ephemeral_block_device` supports the following: * `device_name` - The name of the block device to mount on the instance. -* `virtual_name` - (Optional) The [Instance Store Device - Name](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/InstanceStorage.html#InstanceStoreDeviceNames) - (e.g. `"ephemeral0"`). * `no_device` - (Optional) Suppresses the specified device included in the AMI's block device mapping. +* `virtual_name` - (Optional) [Instance Store Device Name](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/InstanceStorage.html#InstanceStoreDeviceNames) (e.g. `ephemeral0`). -Each AWS Instance type has a different set of Instance Store block devices -available for attachment. AWS [publishes a -list](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/InstanceStorage.html#StorageOnInstanceTypes) -of which ephemeral devices are available on each type. The devices are always -identified by the `virtual_name` in the format `"ephemeral{0..N}"`. - -### Network Interfaces - -Each of the `network_interface` blocks attach a network interface to an EC2 Instance during boot time. However, because -the network interface is attached at boot-time, replacing/modifying the network interface **WILL** trigger a recreation -of the EC2 Instance. If you should need at any point to detach/modify/re-attach a network interface to the instance, use -the `aws_network_interface` or `aws_network_interface_attachment` resources instead. - -The `network_interface` configuration block _does_, however, allow users to supply their own network interface to be used -as the default network interface on an EC2 Instance, attached at `eth0`. - -Each `network_interface` block supports the following: - -* `device_index` - (Required) The integer index of the network interface attachment. Limited by instance type. -* `network_interface_id` - (Required) The ID of the network interface to attach. -* `delete_on_termination` - (Optional) Whether or not to delete the network interface on instance termination. Defaults to `false`. Currently, the only valid value is `false`, as this is only supported when creating new network interfaces when launching an instance. - -### Credit Specification - -~> **NOTE:** Removing this configuration on existing instances will only stop managing it. It will not change the configuration back to the default for the instance type. - -Credit specification can be applied/modified to the EC2 Instance at any time. - -The `credit_specification` block supports the following: - -* `cpu_credits` - (Optional) The credit option for CPU usage. Can be `"standard"` or `"unlimited"`. T3 instances are launched as unlimited by default. T2 instances are launched as standard by default. - -### Metadata Options - -Metadata options can be applied/modified to the EC2 Instance at any time. - -The `metadata_options` block supports the following: - -* `http_endpoint` - (Optional) Whether the metadata service is available. Can be `"enabled"` or `"disabled"`. (Default: `"enabled"`). -* `http_tokens` - (Optional) Whether or not the metadata service requires session tokens, also referred to as _Instance Metadata Service Version 2 (IMDSv2)_. Can be `"optional"` or `"required"`. (Default: `"optional"`). -* `http_put_response_hop_limit` - (Optional) The desired HTTP PUT response hop limit for instance metadata requests. The larger the number, the further instance metadata requests can travel. Can be an integer from `1` to `64`. (Default: `1`). - -For more information, see the documentation on the [Instance Metadata Service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html). +Each AWS Instance type has a different set of Instance Store block devices available for attachment. AWS [publishes a list](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/InstanceStorage.html#StorageOnInstanceTypes) of which ephemeral devices are available on each type. The devices are always identified by the `virtual_name` in the format `ephemeral{0..N}`. ### Enclave Options @@ -219,89 +203,55 @@ Enclave options apply to the instance at boot time. The `enclave_options` block supports the following: -* `enabled` - (Optional) Whether Nitro Enclaves will be enabled on the instance. (Default: `"false"`). +* `enabled` - (Optional) Whether Nitro Enclaves will be enabled on the instance. Defaults to `false`. For more information, see the documentation on [Nitro Enclaves](https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave.html). -### Example +### Metadata Options -```hcl -resource "aws_vpc" "my_vpc" { - cidr_block = "172.16.0.0/16" +Metadata options can be applied/modified to the EC2 Instance at any time. - tags = { - Name = "tf-example" - } -} +The `metadata_options` block supports the following: -resource "aws_subnet" "my_subnet" { - vpc_id = aws_vpc.my_vpc.id - cidr_block = "172.16.10.0/24" - availability_zone = "us-west-2a" +* `http_endpoint` - (Optional) Whether the metadata service is available. Valid values include `enabled` or `disabled`. Defaults to `enabled`. +* `http_put_response_hop_limit` - (Optional) Desired HTTP PUT response hop limit for instance metadata requests. The larger the number, the further instance metadata requests can travel. Valid values are integer from `1` to `64`. Defaults to `1`. +* `http_tokens` - (Optional) Whether or not the metadata service requires session tokens, also referred to as _Instance Metadata Service Version 2 (IMDSv2)_. Valid values include `optional` or `required`. Defaults to `optional`. - tags = { - Name = "tf-example" - } -} +For more information, see the documentation on the [Instance Metadata Service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html). -resource "aws_network_interface" "foo" { - subnet_id = aws_subnet.my_subnet.id - private_ips = ["172.16.10.100"] +### Network Interfaces - tags = { - Name = "primary_network_interface" - } -} +Each of the `network_interface` blocks attach a network interface to an EC2 Instance during boot time. However, because the network interface is attached at boot-time, replacing/modifying the network interface **WILL** trigger a recreation of the EC2 Instance. If you should need at any point to detach/modify/re-attach a network interface to the instance, use the `aws_network_interface` or `aws_network_interface_attachment` resources instead. -resource "aws_instance" "foo" { - ami = "ami-005e54dee72cc1d00" # us-west-2 - instance_type = "t2.micro" +The `network_interface` configuration block _does_, however, allow users to supply their own network interface to be used as the default network interface on an EC2 Instance, attached at `eth0`. - network_interface { - network_interface_id = aws_network_interface.foo.id - device_index = 0 - } +Each `network_interface` block supports the following: - credit_specification { - cpu_credits = "unlimited" - } -} -``` +* `delete_on_termination` - (Optional) Whether or not to delete the network interface on instance termination. Defaults to `false`. Currently, the only valid value is `false`, as this is only supported when creating new network interfaces when launching an instance. +* `device_index` - (Required) Integer index of the network interface attachment. Limited by instance type. +* `network_interface_id` - (Required) ID of the network interface to attach. ## Attributes Reference In addition to all arguments above, the following attributes are exported: -* `id` - The instance ID. * `arn` - The ARN of the instance. -* `availability_zone` - The availability zone of the instance. -* `placement_group` - The placement group of the instance. -* `key_name` - The key name of the instance -* `password_data` - Base-64 encoded encrypted password data for the instance. - Useful for getting the administrator password for instances running Microsoft Windows. - This attribute is only exported if `get_password_data` is true. - Note that this encrypted value will be stored in the state file, as with all exported attributes. - See [GetPasswordData](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_GetPasswordData.html) for more information. -* `public_dns` - The public DNS name assigned to the instance. For EC2-VPC, this - is only available if you've enabled DNS hostnames for your VPC -* `public_ip` - The public IP address assigned to the instance, if applicable. **NOTE**: If you are using an [`aws_eip`](/docs/providers/aws/r/eip.html) with your instance, you should refer to the EIP's address directly and not use `public_ip`, as this field will change after the EIP is attached. -* `ipv6_addresses` - A list of assigned IPv6 addresses, if any -* `primary_network_interface_id` - The ID of the instance's primary network interface. -* `private_dns` - The private DNS name assigned to the instance. Can only be - used inside the Amazon EC2, and only available if you've enabled DNS hostnames - for your VPC -* `private_ip` - The private IP address assigned to the instance -* `security_groups` - The associated security groups. -* `vpc_security_group_ids` - The associated security groups in non-default VPC -* `subnet_id` - The VPC subnet ID. -* `outpost_arn` - The ARN of the Outpost the instance is assigned to. -* `credit_specification` - Credit specification of instance. * `instance_state` - The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped`. See [Instance Lifecycle](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-lifecycle.html) for more information. +* `outpost_arn` - The ARN of the Outpost the instance is assigned to. +* `password_data` - Base-64 encoded encrypted password data for the instance. Useful for getting the administrator password for instances running Microsoft Windows. This attribute is only exported if `get_password_data` is true. Note that this encrypted value will be stored in the state file, as with all exported attributes. See [GetPasswordData](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_GetPasswordData.html) for more information. +* `primary_network_interface_id` - The ID of the instance's primary network interface. +* `private_dns` - The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC. +* `public_dns` - The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC. +* `public_ip` - The public IP address assigned to the instance, if applicable. **NOTE**: If you are using an [`aws_eip`](/docs/providers/aws/r/eip.html) with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached. + +For `ebs_block_device`, in addition to the arguments above, the following attribute is exported: + +* `volume_id` - ID of the volume. For example, the ID can be accessed like this, `aws_instance.web.ebs_block_device.2.volume_id`. -For any `root_block_device` and `ebs_block_device` the `volume_id` is exported. -e.g. `aws_instance.web.root_block_device.0.volume_id` +For `root_block_device`, in addition to the arguments above, the following attributes are exported: -For the `root_block_device` the `device_name` is exported. +* `volume_id` - ID of the volume. For example, the ID can be accessed like this, `aws_instance.web.root_block_device.0.volume_id`. +* `device_name` - Device name, e.g. `/dev/sdh` or `xvdh`. ## Import