diff --git a/.changelog/25161.txt b/.changelog/25161.txt new file mode 100644 index 00000000000..35a37cb4813 --- /dev/null +++ b/.changelog/25161.txt @@ -0,0 +1,23 @@ +```release-note:enhancement +resource/aws_instance: Add `private_dns_name_options` argument +``` + +```release-note:enhancement +data-source/aws_instance: Add `private_dns_name_options` attribute +``` + +```release-note:enhancement +resource/aws_instance: Correctly handle `credit_specification` for T4g instances +``` + +```release-note:enhancement +data-source/aws_instance: Correctly set `credit_specification` for T4g instances +``` + +```release-note:enhancement +resource/aws_launch_template: Correctly handle `credit_specification` for T4g instances +``` + +```release-note:enhancement +data-source/aws_launch_template: Correctly set `credit_specification` for T4g instances +``` \ No newline at end of file diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go index a1be9da8ad7..5c3ed092d4a 100644 --- a/internal/acctest/acctest.go +++ b/internal/acctest/acctest.go @@ -1690,6 +1690,44 @@ data "aws_ami" "amzn-ami-minimal-hvm-ebs" { ` } +func configLatestAmazonLinux2HVMEBSAMI(architecture string) string { + return fmt.Sprintf(` +data "aws_ami" "amzn2-ami-minimal-hvm-ebs-%[1]s" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["amzn2-ami-minimal-hvm-*"] + } + + filter { + name = "root-device-type" + values = ["ebs"] + } + + filter { + name = "architecture" + values = [%[1]q] + } +} +`, architecture) +} + +// ConfigLatestAmazonLinux2HVMEBSX8664AMI returns the configuration for a data source that +// describes the latest Amazon Linux 2 x86_64 AMI using HVM virtualization and an EBS root device. +// The data source is named 'amzn2-ami-minimal-hvm-ebs-x86_64'. +func ConfigLatestAmazonLinux2HVMEBSX8664AMI() string { + return configLatestAmazonLinux2HVMEBSAMI(ec2.ArchitectureValuesX8664) +} + +// ConfigLatestAmazonLinux2HVMEBSARM64AMI returns the configuration for a data source that +// describes the latest Amazon Linux 2 arm64 AMI using HVM virtualization and an EBS root device. +// The data source is named 'amzn2-ami-minimal-hvm-ebs-arm64'. +func ConfigLatestAmazonLinux2HVMEBSARM64AMI() string { + return configLatestAmazonLinux2HVMEBSAMI(ec2.ArchitectureValuesArm64) +} + func ConfigLambdaBase(policyName, roleName, sgName string) string { return fmt.Sprintf(` data "aws_partition" "current" {} diff --git a/internal/service/autoscaling/group_test.go b/internal/service/autoscaling/group_test.go index 3d32d0e9db6..c274b56acf3 100644 --- a/internal/service/autoscaling/group_test.go +++ b/internal/service/autoscaling/group_test.go @@ -4729,19 +4729,12 @@ resource "aws_autoscaling_group" "test" { } func testAccGroupConfig_mixedInstancesPolicyLaunchTemplateOverrideInstanceTypeLaunchTemplateSpecification(rName string) string { - return acctest.ConfigCompose(testAccGroupLaunchTemplateBaseConfig(rName, "t3.micro"), fmt.Sprintf(` -data "aws_ami" "amzn-ami-hvm-arm64-gp2" { - most_recent = true - owners = ["amazon"] - - filter { - name = "name" - values = ["amzn2-ami-hvm-*-arm64-gp2"] - } -} - + return acctest.ConfigCompose( + testAccGroupLaunchTemplateBaseConfig(rName, "t3.micro"), + acctest.ConfigLatestAmazonLinux2HVMEBSARM64AMI(), + fmt.Sprintf(` resource "aws_launch_template" "test-arm" { - image_id = data.aws_ami.amzn-ami-hvm-arm64-gp2.id + image_id = data.aws_ami.amzn2-ami-minimal-hvm-ebs-arm64.id instance_type = "t4g.micro" name = "%[1]s-arm" } diff --git a/internal/service/ec2/ec2_instance.go b/internal/service/ec2/ec2_instance.go index 268e49d0efe..4b4a7878489 100644 --- a/internal/service/ec2/ec2_instance.go +++ b/internal/service/ec2/ec2_instance.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "log" + "regexp" "strconv" "strings" "time" @@ -136,8 +137,9 @@ func ResourceInstance() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "cpu_credits": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(CPUCredits_Values(), false), DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { // Only work with existing instances if d.Id() == "" { @@ -505,6 +507,36 @@ func ResourceInstance() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "private_dns_name_options": { + Type: schema.TypeList, + Optional: true, + Computed: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enable_resource_name_dns_aaaa_record": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + ForceNew: true, + }, + "enable_resource_name_dns_a_record": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + ForceNew: true, + }, + "hostname_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(ec2.HostnameType_Values(), false), + }, + }, + }, + }, "private_ip": { Type: schema.TypeString, Optional: true, @@ -798,6 +830,7 @@ func resourceInstanceCreate(d *schema.ResourceData, meta interface{}) error { Monitoring: instanceOpts.Monitoring, NetworkInterfaces: instanceOpts.NetworkInterfaces, Placement: instanceOpts.Placement, + PrivateDnsNameOptions: instanceOpts.PrivateDNSNameOptions, PrivateIpAddress: instanceOpts.PrivateIPAddress, SecurityGroupIds: instanceOpts.SecurityGroupIDs, SecurityGroups: instanceOpts.SecurityGroups, @@ -908,6 +941,13 @@ func resourceInstanceRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("reading EC2 Instance (%s): %w", d.Id(), err) } + instanceType := aws.StringValue(instance.InstanceType) + instanceTypeInfo, err := FindInstanceTypeByName(conn, instanceType) + + if err != nil { + return fmt.Errorf("reading EC2 Instance Type (%s): %w", instanceType, err) + } + d.Set("instance_state", instance.State.Name) if v := instance.Placement; v != nil { @@ -955,8 +995,16 @@ func resourceInstanceRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("error setting metadata_options: %w", err) } + if instance.PrivateDnsNameOptions != nil { + if err := d.Set("private_dns_name_options", []interface{}{flattenPrivateDNSNameOptionsResponse(instance.PrivateDnsNameOptions)}); err != nil { + return fmt.Errorf("error setting private_dns_name_options: %w", err) + } + } else { + d.Set("private_dns_name_options", nil) + } + d.Set("ami", instance.ImageId) - d.Set("instance_type", instance.InstanceType) + d.Set("instance_type", instanceType) d.Set("key_name", instance.KeyName) d.Set("public_dns", instance.PublicDnsName) d.Set("public_ip", instance.PublicIpAddress) @@ -1184,7 +1232,7 @@ func resourceInstanceRead(d *schema.ResourceData, meta interface{}) error { // AWS Standard will return InstanceCreditSpecification.NotSupported errors for EC2 Instance IDs outside T2 and T3 instance types // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/8055 - if strings.HasPrefix(aws.StringValue(instance.InstanceType), "t2") || strings.HasPrefix(aws.StringValue(instance.InstanceType), "t3") { + if aws.BoolValue(instanceTypeInfo.BurstablePerformanceSupported) { instanceCreditSpecification, err := FindInstanceCreditSpecificationByID(conn, d.Id()) // Ignore UnsupportedOperation errors for AWS China and GovCloud (US). @@ -2454,10 +2502,13 @@ func getInstancePasswordData(instanceID string, conn *ec2.EC2) (string, error) { type awsInstanceOpts struct { BlockDeviceMappings []*ec2.BlockDeviceMapping CapacityReservationSpecification *ec2.CapacityReservationSpecification + CpuOptions *ec2.CpuOptionsRequest + CreditSpecification *ec2.CreditSpecificationRequest DisableAPIStop *bool DisableAPITermination *bool EBSOptimized *bool - Monitoring *ec2.RunInstancesMonitoringEnabled + EnclaveOptions *ec2.EnclaveOptionsRequest + HibernationOptions *ec2.HibernationOptionsRequest IAMInstanceProfile *ec2.IamInstanceProfileSpecification ImageID *string InstanceInitiatedShutdownBehavior *string @@ -2466,20 +2517,18 @@ type awsInstanceOpts struct { Ipv6Addresses []*ec2.InstanceIpv6Address KeyName *string LaunchTemplate *ec2.LaunchTemplateSpecification + MaintenanceOptions *ec2.InstanceMaintenanceOptionsRequest + MetadataOptions *ec2.InstanceMetadataOptionsRequest + Monitoring *ec2.RunInstancesMonitoringEnabled NetworkInterfaces []*ec2.InstanceNetworkInterfaceSpecification Placement *ec2.Placement + PrivateDNSNameOptions *ec2.PrivateDnsNameOptionsRequest PrivateIPAddress *string SecurityGroupIDs []*string SecurityGroups []*string SpotPlacement *ec2.SpotPlacement SubnetID *string UserData64 *string - CreditSpecification *ec2.CreditSpecificationRequest - CpuOptions *ec2.CpuOptionsRequest - HibernationOptions *ec2.HibernationOptionsRequest - MetadataOptions *ec2.InstanceMetadataOptionsRequest - EnclaveOptions *ec2.EnclaveOptionsRequest - MaintenanceOptions *ec2.InstanceMaintenanceOptionsRequest } func buildInstanceOpts(d *schema.ResourceData, meta interface{}) (*awsInstanceOpts, error) { @@ -2489,8 +2538,8 @@ func buildInstanceOpts(d *schema.ResourceData, meta interface{}) (*awsInstanceOp DisableAPIStop: aws.Bool(d.Get("disable_api_stop").(bool)), DisableAPITermination: aws.Bool(d.Get("disable_api_termination").(bool)), EBSOptimized: aws.Bool(d.Get("ebs_optimized").(bool)), - MetadataOptions: expandInstanceMetadataOptions(d.Get("metadata_options").([]interface{})), EnclaveOptions: expandEnclaveOptions(d.Get("enclave_options").([]interface{})), + MetadataOptions: expandInstanceMetadataOptions(d.Get("metadata_options").([]interface{})), } if v, ok := d.GetOk("ami"); ok { @@ -2520,23 +2569,30 @@ func buildInstanceOpts(d *schema.ResourceData, meta interface{}) (*awsInstanceOp instanceType := d.Get("instance_type").(string) - // Set default cpu_credits as Unlimited for T3 instance type + // Set default cpu_credits as Unlimited for T3/T3a instance type if strings.HasPrefix(instanceType, "t3") { opts.CreditSpecification = &ec2.CreditSpecificationRequest{ - CpuCredits: aws.String("unlimited"), + CpuCredits: aws.String(CPUCreditsUnlimited), } } if v, ok := d.GetOk("credit_specification"); ok && len(v.([]interface{})) > 0 { - // Only T2 and T3 are burstable performance instance types and supports Unlimited. - if strings.HasPrefix(instanceType, "t2") || strings.HasPrefix(instanceType, "t3") { - if v, ok := v.([]interface{})[0].(map[string]interface{}); ok { - opts.CreditSpecification = expandCreditSpecificationRequest(v) + if instanceType != "" { + instanceTypeInfo, err := FindInstanceTypeByName(conn, instanceType) + + if err != nil { + return nil, fmt.Errorf("reading EC2 Instance Type (%s): %w", instanceType, err) + } + + if aws.BoolValue(instanceTypeInfo.BurstablePerformanceSupported) { + if v, ok := v.([]interface{})[0].(map[string]interface{}); ok { + opts.CreditSpecification = expandCreditSpecificationRequest(v) + } else { + log.Print("[WARN] credit_specification is defined but the value of cpu_credits is missing, default value will be used.") + } } else { - log.Print("[WARN] credit_specification is defined but the value of cpu_credits is missing, default value will be used.") + log.Print("[WARN] credit_specification is defined but instance type does not support burstable performance. Ignoring...") } - } else { - log.Print("[WARN] credit_specification is defined but instance type is not T2/T3. Ignoring...") } } @@ -2687,6 +2743,10 @@ func buildInstanceOpts(d *schema.ResourceData, meta interface{}) (*awsInstanceOp opts.MaintenanceOptions = expandInstanceMaintenanceOptionsRequest(v.([]interface{})[0].(map[string]interface{})) } + if v, ok := d.GetOk("private_dns_name_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + opts.PrivateDNSNameOptions = expandPrivateDNSNameOptionsRequest(v.([]interface{})[0].(map[string]interface{})) + } + return opts, nil } @@ -3080,6 +3140,50 @@ func flattenInstanceMaintenanceOptions(apiObject *ec2.InstanceMaintenanceOptions return tfMap } +func expandPrivateDNSNameOptionsRequest(tfMap map[string]interface{}) *ec2.PrivateDnsNameOptionsRequest { + if tfMap == nil { + return nil + } + + apiObject := &ec2.PrivateDnsNameOptionsRequest{} + + if v, ok := tfMap["enable_resource_name_dns_aaaa_record"].(bool); ok { + apiObject.EnableResourceNameDnsAAAARecord = aws.Bool(v) + } + + if v, ok := tfMap["enable_resource_name_dns_a_record"].(bool); ok { + apiObject.EnableResourceNameDnsARecord = aws.Bool(v) + } + + if v, ok := tfMap["hostname_type"].(string); ok && v != "" { + apiObject.HostnameType = aws.String(v) + } + + return apiObject +} + +func flattenPrivateDNSNameOptionsResponse(apiObject *ec2.PrivateDnsNameOptionsResponse) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.EnableResourceNameDnsAAAARecord; v != nil { + tfMap["enable_resource_name_dns_aaaa_record"] = aws.BoolValue(v) + } + + if v := apiObject.EnableResourceNameDnsARecord; v != nil { + tfMap["enable_resource_name_dns_a_record"] = aws.BoolValue(v) + } + + if v := apiObject.HostnameType; v != nil { + tfMap["hostname_type"] = aws.StringValue(v) + } + + return tfMap +} + func expandLaunchTemplateSpecification(tfMap map[string]interface{}) *ec2.LaunchTemplateSpecification { if tfMap == nil { return nil @@ -3260,3 +3364,39 @@ func findInstanceTagValue(conn *ec2.EC2, instanceID, tagKey string) (string, err func isSnowballEdgeInstance(id string) bool { return strings.Contains(id, "s.") } + +// InstanceType describes an EC2 instance type. +type InstanceType struct { + // e.g. "m6i" + Type string + // e.g. "m" + Family string + // e.g. 6 + Generation int + // e.g. "i" + AdditionalCapabilities string + // e.g. "9xlarge" + Size string +} + +func ParseInstanceType(s string) (*InstanceType, error) { + matches := regexp.MustCompile(`(([[:alpha:]]+)([[:digit:]])+([[:alpha:]]*))\.([[:alnum:]]+)`).FindStringSubmatch(s) + + if matches == nil { + return nil, fmt.Errorf("invalid EC2 Instance Type name: %s", s) + } + + generation, err := strconv.Atoi(matches[3]) + + if err != nil { + return nil, err + } + + return &InstanceType{ + Type: matches[1], + Family: matches[2], + Generation: generation, + AdditionalCapabilities: matches[4], + Size: matches[5], + }, nil +} diff --git a/internal/service/ec2/ec2_instance_data_source.go b/internal/service/ec2/ec2_instance_data_source.go index a7d41c5ff7b..33d2e018cda 100644 --- a/internal/service/ec2/ec2_instance_data_source.go +++ b/internal/service/ec2/ec2_instance_data_source.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "log" - "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" @@ -260,6 +259,26 @@ func DataSourceInstance() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "private_dns_name_options": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enable_resource_name_dns_aaaa_record": { + Type: schema.TypeBool, + Computed: true, + }, + "enable_resource_name_dns_a_record": { + Type: schema.TypeBool, + Computed: true, + }, + "hostname_type": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, "public_dns": { Type: schema.TypeString, Computed: true, @@ -418,6 +437,14 @@ func dataSourceInstanceRead(d *schema.ResourceData, meta interface{}) error { // Populate instance attribute fields with the returned instance func instanceDescriptionAttributes(d *schema.ResourceData, instance *ec2.Instance, conn *ec2.EC2, ignoreTagsConfig *tftags.IgnoreConfig) error { d.SetId(aws.StringValue(instance.InstanceId)) + + instanceType := aws.StringValue(instance.InstanceType) + instanceTypeInfo, err := FindInstanceTypeByName(conn, instanceType) + + if err != nil { + return fmt.Errorf("reading EC2 Instance Type (%s): %w", instanceType, err) + } + // Set the easy attributes d.Set("instance_state", instance.State.Name) if instance.Placement != nil { @@ -437,7 +464,7 @@ func instanceDescriptionAttributes(d *schema.ResourceData, instance *ec2.Instanc } d.Set("ami", instance.ImageId) - d.Set("instance_type", instance.InstanceType) + d.Set("instance_type", instanceType) d.Set("key_name", instance.KeyName) d.Set("outpost_arn", instance.OutpostArn) d.Set("private_dns", instance.PrivateDnsName) @@ -555,7 +582,7 @@ func instanceDescriptionAttributes(d *schema.ResourceData, instance *ec2.Instanc // AWS Standard will return InstanceCreditSpecification.NotSupported errors for EC2 Instance IDs outside T2 and T3 instance types // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/8055 - if strings.HasPrefix(aws.StringValue(instance.InstanceType), "t2") || strings.HasPrefix(aws.StringValue(instance.InstanceType), "t3") { + if aws.BoolValue(instanceTypeInfo.BurstablePerformanceSupported) { instanceCreditSpecification, err := FindInstanceCreditSpecificationByID(conn, d.Id()) // Ignore UnsupportedOperation errors for AWS China and GovCloud (US). @@ -595,5 +622,13 @@ func instanceDescriptionAttributes(d *schema.ResourceData, instance *ec2.Instanc return fmt.Errorf("error setting metadata_options: %w", err) } + if instance.PrivateDnsNameOptions != nil { + if err := d.Set("private_dns_name_options", []interface{}{flattenPrivateDNSNameOptionsResponse(instance.PrivateDnsNameOptions)}); err != nil { + return fmt.Errorf("error setting private_dns_name_options: %w", err) + } + } else { + d.Set("private_dns_name_options", nil) + } + return nil } diff --git a/internal/service/ec2/ec2_instance_data_source_test.go b/internal/service/ec2/ec2_instance_data_source_test.go index 71d2800002f..5d4b9b1c5ea 100644 --- a/internal/service/ec2/ec2_instance_data_source_test.go +++ b/internal/service/ec2/ec2_instance_data_source_test.go @@ -232,6 +232,10 @@ func TestAccEC2InstanceDataSource_privateIP(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair(datasourceName, "ami", resourceName, "ami"), resource.TestCheckResourceAttrPair(datasourceName, "instance_type", resourceName, "instance_type"), + resource.TestCheckResourceAttrPair(datasourceName, "private_dns_name_options.#", resourceName, "private_dns_name_options.#"), + resource.TestCheckResourceAttrPair(datasourceName, "private_dns_name_options.0.enable_resource_name_dns_aaaa_record", resourceName, "private_dns_name_options.0.enable_resource_name_dns_aaaa_record"), + resource.TestCheckResourceAttrPair(datasourceName, "private_dns_name_options.0.enable_resource_name_dns_a_record", resourceName, "private_dns_name_options.0.enable_resource_name_dns_a_record"), + resource.TestCheckResourceAttrPair(datasourceName, "private_dns_name_options.0.hostname_type", resourceName, "private_dns_name_options.0.hostname_type"), resource.TestCheckResourceAttrPair(datasourceName, "private_ip", resourceName, "private_ip"), ), }, diff --git a/internal/service/ec2/ec2_instance_test.go b/internal/service/ec2/ec2_instance_test.go index 45c82f70719..dab1e953f04 100644 --- a/internal/service/ec2/ec2_instance_test.go +++ b/internal/service/ec2/ec2_instance_test.go @@ -100,6 +100,75 @@ func TestFetchRootDevice(t *testing.T) { } } +func TestParseInstanceType(t *testing.T) { + invalidInstanceTypes := []string{ + "", + "abc", + "abc4", + "abc4.", + "abc.xlarge", + "4g.3xlarge", + } + + for _, v := range invalidInstanceTypes { + if _, err := tfec2.ParseInstanceType(v); err == nil { + t.Errorf("Expected error for %s", v) + } + } + + v, err := tfec2.ParseInstanceType("c4.large") + + if err != nil { + t.Errorf("Unexpected error: %s", err) + } + + if got, want := v.Type, "c4"; got != want { + t.Errorf("Got: %s, want: %s", got, want) + } + + if got, want := v.Family, "c"; got != want { + t.Errorf("Got: %s, want: %s", got, want) + } + + if got, want := v.Generation, 4; got != want { + t.Errorf("Got: %d, want: %d", got, want) + } + + if got, want := v.AdditionalCapabilities, ""; got != want { + t.Errorf("Got: %s, want: %s", got, want) + } + + if got, want := v.Size, "large"; got != want { + t.Errorf("Got: %s, want: %s", got, want) + } + + v, err = tfec2.ParseInstanceType("im4gn.16xlarge") + + if err != nil { + t.Errorf("Unexpected error: %s", err) + } + + if got, want := v.Type, "im4gn"; got != want { + t.Errorf("Got: %s, want: %s", got, want) + } + + if got, want := v.Family, "im"; got != want { + t.Errorf("Got: %s, want: %s", got, want) + } + + if got, want := v.Generation, 4; got != want { + t.Errorf("Got: %d, want: %d", got, want) + } + + if got, want := v.AdditionalCapabilities, "gn"; got != want { + t.Errorf("Got: %s, want: %s", got, want) + } + + if got, want := v.Size, "16xlarge"; got != want { + t.Errorf("Got: %s, want: %s", got, want) + } +} + func TestAccEC2Instance_basic(t *testing.T) { var v ec2.Instance resourceName := "aws_instance.test" @@ -1744,6 +1813,68 @@ func TestAccEC2Instance_Empty_privateIP(t *testing.T) { }) } +func TestAccEC2Instance_PrivateDNSNameOptions_computed(t *testing.T) { + var v ec2.Instance + resourceName := "aws_instance.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceConfig_PrivateDNSNameOptions_computed(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "private_dns_name_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "private_dns_name_options.0.enable_resource_name_dns_aaaa_record", "true"), + resource.TestCheckResourceAttr(resourceName, "private_dns_name_options.0.enable_resource_name_dns_a_record", "true"), + resource.TestCheckResourceAttr(resourceName, "private_dns_name_options.0.hostname_type", "resource-name"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"user_data_replace_on_change"}, + }, + }, + }) +} + +func TestAccEC2Instance_PrivateDNSNameOptions_configured(t *testing.T) { + var v ec2.Instance + resourceName := "aws_instance.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceConfig_PrivateDNSNameOptions_configured(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "private_dns_name_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "private_dns_name_options.0.enable_resource_name_dns_aaaa_record", "false"), + resource.TestCheckResourceAttr(resourceName, "private_dns_name_options.0.enable_resource_name_dns_a_record", "true"), + resource.TestCheckResourceAttr(resourceName, "private_dns_name_options.0.hostname_type", "ip-name"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"user_data_replace_on_change"}, + }, + }, + }) +} + // Guard against regression with KeyPairs // https://github.com/hashicorp/terraform/issues/2302 func TestAccEC2Instance_keyPairCheck(t *testing.T) { @@ -3589,6 +3720,64 @@ func TestAccEC2Instance_CreditSpecificationUnknownCPUCredits_t3(t *testing.T) { }) } +func TestAccEC2Instance_CreditSpecificationUnknownCPUCredits_t3a(t *testing.T) { + var v ec2.Instance + resourceName := "aws_instance.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceConfig_creditSpecificationUnknownCPUCredits(rName, "t3a.micro"), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "credit_specification.#", "1"), + resource.TestCheckResourceAttr(resourceName, "credit_specification.0.cpu_credits", "unlimited"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"user_data_replace_on_change"}, + }, + }, + }) +} + +func TestAccEC2Instance_CreditSpecificationUnknownCPUCredits_t4g(t *testing.T) { + var v ec2.Instance + resourceName := "aws_instance.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceConfig_creditSpecificationUnknownCPUCredits(rName, "t4g.micro"), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "credit_specification.#", "1"), + resource.TestCheckResourceAttr(resourceName, "credit_specification.0.cpu_credits", "unlimited"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"user_data_replace_on_change"}, + }, + }, + }) +} + func TestAccEC2Instance_CreditSpecification_updateCPUCredits(t *testing.T) { var first, second, third ec2.Instance resourceName := "aws_instance.test" @@ -6435,6 +6624,77 @@ resource "aws_instance" "test" { `, rName)) } +func testAccInstancePrivateDNSNameOptionsBaseConfig(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigAvailableAZsNoOptInDefaultExclude(), + fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + assign_generated_ipv6_cidr_block = true + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + cidr_block = cidrsubnet(aws_vpc.test.cidr_block, 8, 0) + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[2] + ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 8, 1) + assign_ipv6_address_on_creation = true + + enable_resource_name_dns_aaaa_record_on_launch = true + enable_resource_name_dns_a_record_on_launch = true + private_dns_hostname_type_on_launch = "resource-name" + + tags = { + Name = %[1]q + } +} +`, rName)) +} + +func testAccInstanceConfig_PrivateDNSNameOptions_computed(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigLatestAmazonLinuxHVMEBSAMI(), + testAccInstancePrivateDNSNameOptionsBaseConfig(rName), + fmt.Sprintf(` +resource "aws_instance" "test" { + ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id + instance_type = "t2.micro" + subnet_id = aws_subnet.test.id + + tags = { + Name = %[1]q + } +} +`, rName)) +} + +func testAccInstanceConfig_PrivateDNSNameOptions_configured(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigLatestAmazonLinuxHVMEBSAMI(), + testAccInstancePrivateDNSNameOptionsBaseConfig(rName), + fmt.Sprintf(` +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_dns_name_options { + enable_resource_name_dns_aaaa_record = false + enable_resource_name_dns_a_record = true + hostname_type = "ip-name" + } + + tags = { + Name = %[1]q + } +} +`, rName)) +} + func testAccInstanceConfig_networkSecurityGroups(rName string) string { return acctest.ConfigCompose( acctest.ConfigLatestAmazonLinuxHVMEBSAMI(), @@ -7279,7 +7539,7 @@ func testAccInstanceConfig_creditSpecificationIsNotAppliedToNonBurstable(rName s fmt.Sprintf(` resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id - instance_type = "t2.small" + instance_type = "c6i.large" subnet_id = aws_subnet.test.id credit_specification { @@ -7294,12 +7554,22 @@ resource "aws_instance" "test" { } func testAccInstanceConfig_creditSpecificationUnknownCPUCredits(rName, instanceType string) string { + var amiConfig, amiIDRef string + + if v, err := tfec2.ParseInstanceType(instanceType); err == nil && v.Type == "t4g" { + amiConfig = acctest.ConfigLatestAmazonLinux2HVMEBSARM64AMI() + amiIDRef = "data.aws_ami.amzn2-ami-minimal-hvm-ebs-arm64.id" + } else { + amiConfig = acctest.ConfigLatestAmazonLinux2HVMEBSX8664AMI() + amiIDRef = "data.aws_ami.amzn2-ami-minimal-hvm-ebs-x86_64.id" + } + return acctest.ConfigCompose( - acctest.ConfigLatestAmazonLinuxHVMEBSAMI(), + amiConfig, testAccInstanceVPCConfig(rName, false, 0), fmt.Sprintf(` resource "aws_instance" "test" { - ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id + ami = %[3]s instance_type = %[2]q subnet_id = aws_subnet.test.id @@ -7309,7 +7579,7 @@ resource "aws_instance" "test" { Name = %[1]q } } -`, rName, instanceType)) +`, rName, instanceType, amiIDRef)) } func testAccInstanceConfig_userDataUnspecified(rName string) string { diff --git a/internal/service/ec2/ec2_instance_type_data_source.go b/internal/service/ec2/ec2_instance_type_data_source.go index a660865283b..c22ab56c6cd 100644 --- a/internal/service/ec2/ec2_instance_type_data_source.go +++ b/internal/service/ec2/ec2_instance_type_data_source.go @@ -1,13 +1,10 @@ package ec2 import ( - "fmt" - "log" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func DataSourceInstanceType() *schema.Resource { @@ -19,125 +16,99 @@ func DataSourceInstanceType() *schema.Resource { Type: schema.TypeBool, Computed: true, }, - "bare_metal": { Type: schema.TypeBool, Computed: true, }, - "burstable_performance_supported": { Type: schema.TypeBool, Computed: true, }, - "current_generation": { Type: schema.TypeBool, Computed: true, }, - "dedicated_hosts_supported": { Type: schema.TypeBool, Computed: true, }, - "default_cores": { Type: schema.TypeInt, Computed: true, - Optional: true, }, - "default_threads_per_core": { Type: schema.TypeInt, Computed: true, - Optional: true, }, - "default_vcpus": { Type: schema.TypeInt, Computed: true, }, - "ebs_encryption_support": { Type: schema.TypeString, Computed: true, }, - "ebs_nvme_support": { Type: schema.TypeString, Computed: true, }, - "ebs_optimized_support": { Type: schema.TypeString, Computed: true, }, - "ebs_performance_baseline_bandwidth": { Type: schema.TypeInt, Computed: true, }, - "ebs_performance_baseline_throughput": { Type: schema.TypeFloat, Computed: true, }, - "ebs_performance_baseline_iops": { Type: schema.TypeInt, Computed: true, }, - "ebs_performance_maximum_bandwidth": { Type: schema.TypeInt, Computed: true, }, - "ebs_performance_maximum_throughput": { Type: schema.TypeFloat, Computed: true, }, - "ebs_performance_maximum_iops": { Type: schema.TypeInt, Computed: true, }, - "efa_supported": { Type: schema.TypeBool, Computed: true, }, - "ena_support": { Type: schema.TypeString, Computed: true, }, - "encryption_in_transit_supported": { Type: schema.TypeBool, Computed: true, }, - "fpgas": { Type: schema.TypeSet, Computed: true, - Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "count": { Type: schema.TypeInt, Computed: true, }, - "manufacturer": { Type: schema.TypeString, Computed: true, }, - "memory_size": { Type: schema.TypeInt, Computed: true, }, - "name": { Type: schema.TypeString, Computed: true, @@ -145,15 +116,12 @@ func DataSourceInstanceType() *schema.Resource { }, }, }, - "free_tier_eligible": { Type: schema.TypeBool, Computed: true, }, - "gpus": { Type: schema.TypeSet, - Optional: true, Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -161,17 +129,14 @@ func DataSourceInstanceType() *schema.Resource { Type: schema.TypeInt, Computed: true, }, - "manufacturer": { Type: schema.TypeString, Computed: true, }, - "memory_size": { Type: schema.TypeInt, Computed: true, }, - "name": { Type: schema.TypeString, Computed: true, @@ -179,58 +144,47 @@ func DataSourceInstanceType() *schema.Resource { }, }, }, - "hibernation_supported": { Type: schema.TypeBool, Computed: true, }, - "hypervisor": { Type: schema.TypeString, Computed: true, - Optional: true, }, - "inference_accelerators": { Type: schema.TypeSet, Computed: true, - Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "count": { Type: schema.TypeInt, Computed: true, }, - - "name": { + "manufacturer": { Type: schema.TypeString, Computed: true, }, - - "manufacturer": { + "name": { Type: schema.TypeString, Computed: true, }, }, }, }, - "instance_disks": { Type: schema.TypeSet, Computed: true, - Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "count": { Type: schema.TypeInt, Computed: true, }, - "size": { Type: schema.TypeInt, Computed: true, }, - "type": { Type: schema.TypeString, Computed: true, @@ -238,107 +192,84 @@ func DataSourceInstanceType() *schema.Resource { }, }, }, - "instance_storage_supported": { Type: schema.TypeBool, Computed: true, }, - "instance_type": { Type: schema.TypeString, Required: true, }, - "ipv6_supported": { Type: schema.TypeBool, Computed: true, }, - "maximum_ipv4_addresses_per_interface": { Type: schema.TypeInt, Computed: true, }, - "maximum_ipv6_addresses_per_interface": { Type: schema.TypeInt, Computed: true, - Optional: true, }, - "maximum_network_interfaces": { Type: schema.TypeInt, Computed: true, }, - "memory_size": { Type: schema.TypeInt, Computed: true, }, - "network_performance": { Type: schema.TypeString, Computed: true, }, - "supported_architectures": { Type: schema.TypeList, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "supported_placement_strategies": { Type: schema.TypeList, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "supported_root_device_types": { Type: schema.TypeList, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "supported_usages_classes": { Type: schema.TypeList, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "supported_virtualization_types": { Type: schema.TypeList, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "sustained_clock_speed": { Type: schema.TypeFloat, Computed: true, }, - "total_fpga_memory": { Type: schema.TypeInt, Computed: true, - Optional: true, }, - "total_gpu_memory": { Type: schema.TypeInt, Computed: true, - Optional: true, }, - "total_instance_storage": { Type: schema.TypeInt, Computed: true, - Optional: true, }, - "valid_cores": { Type: schema.TypeList, Computed: true, Elem: &schema.Schema{Type: schema.TypeInt}, }, - "valid_threads_per_core": { Type: schema.TypeList, Computed: true, @@ -351,23 +282,13 @@ func DataSourceInstanceType() *schema.Resource { func dataSourceInstanceTypeRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - params := &ec2.DescribeInstanceTypesInput{} - - instanceType := d.Get("instance_type").(string) - params.InstanceTypes = []*string{aws.String(instanceType)} - log.Printf("[DEBUG] Reading instances types: %s", params) + v, err := FindInstanceTypeByName(conn, d.Get("instance_type").(string)) - resp, err := conn.DescribeInstanceTypes(params) if err != nil { - return err - } - if len(resp.InstanceTypes) == 0 { - return fmt.Errorf("no Instance Type found for %s", instanceType) - } - if len(resp.InstanceTypes) > 1 { - return fmt.Errorf("multiple instance types found for type %s", instanceType) + return tfresource.SingularDataSourceFindError("EC2 Instance Type", err) } - v := resp.InstanceTypes[0] + + d.SetId(aws.StringValue(v.InstanceType)) d.Set("auto_recovery_supported", v.AutoRecoverySupported) d.Set("bare_metal", v.BareMetal) d.Set("burstable_performance_supported", v.BurstablePerformanceSupported) @@ -464,6 +385,6 @@ func dataSourceInstanceTypeRead(d *schema.ResourceData, meta interface{}) error d.Set("sustained_clock_speed", v.ProcessorInfo.SustainedClockSpeedInGhz) d.Set("valid_cores", v.VCpuInfo.ValidCores) d.Set("valid_threads_per_core", v.VCpuInfo.ValidThreadsPerCore) - d.SetId(aws.StringValue(v.InstanceType)) + return nil } diff --git a/internal/service/ec2/ec2_instance_type_data_source_test.go b/internal/service/ec2/ec2_instance_type_data_source_test.go index a814f4b9984..11f954cb098 100644 --- a/internal/service/ec2/ec2_instance_type_data_source_test.go +++ b/internal/service/ec2/ec2_instance_type_data_source_test.go @@ -9,7 +9,8 @@ import ( ) func TestAccEC2InstanceTypeDataSource_basic(t *testing.T) { - resourceBasic := "data.aws_ec2_instance_type.basic" + dataSourceName := "data.aws_ec2_instance_type.test" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), @@ -18,50 +19,50 @@ func TestAccEC2InstanceTypeDataSource_basic(t *testing.T) { { Config: testAccInstanceTypeDataSourceConfig_basic, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(resourceBasic, "auto_recovery_supported", "true"), - resource.TestCheckResourceAttr(resourceBasic, "bare_metal", "false"), - resource.TestCheckResourceAttr(resourceBasic, "burstable_performance_supported", "false"), - resource.TestCheckResourceAttr(resourceBasic, "current_generation", "true"), - resource.TestCheckResourceAttr(resourceBasic, "dedicated_hosts_supported", "true"), - resource.TestCheckResourceAttr(resourceBasic, "default_cores", "1"), - resource.TestCheckResourceAttr(resourceBasic, "default_threads_per_core", "2"), - resource.TestCheckResourceAttr(resourceBasic, "default_vcpus", "2"), - resource.TestCheckResourceAttr(resourceBasic, "ebs_encryption_support", "supported"), - resource.TestCheckResourceAttr(resourceBasic, "ebs_nvme_support", "required"), - resource.TestCheckResourceAttr(resourceBasic, "ebs_optimized_support", "default"), - resource.TestCheckResourceAttr(resourceBasic, "efa_supported", "false"), - resource.TestCheckResourceAttr(resourceBasic, "ena_support", "required"), - resource.TestCheckResourceAttr(resourceBasic, "encryption_in_transit_supported", "false"), - resource.TestCheckResourceAttr(resourceBasic, "free_tier_eligible", "false"), - resource.TestCheckResourceAttr(resourceBasic, "hibernation_supported", "true"), - resource.TestCheckResourceAttr(resourceBasic, "hypervisor", "nitro"), - resource.TestCheckResourceAttr(resourceBasic, "instance_storage_supported", "false"), - resource.TestCheckResourceAttr(resourceBasic, "instance_type", "m5.large"), - resource.TestCheckResourceAttr(resourceBasic, "ipv6_supported", "true"), - resource.TestCheckResourceAttr(resourceBasic, "maximum_ipv4_addresses_per_interface", "10"), - resource.TestCheckResourceAttr(resourceBasic, "maximum_ipv6_addresses_per_interface", "10"), - resource.TestCheckResourceAttr(resourceBasic, "maximum_network_interfaces", "3"), - resource.TestCheckResourceAttr(resourceBasic, "memory_size", "8192"), - resource.TestCheckResourceAttr(resourceBasic, "network_performance", "Up to 10 Gigabit"), - resource.TestCheckResourceAttr(resourceBasic, "supported_architectures.#", "1"), - resource.TestCheckResourceAttr(resourceBasic, "supported_architectures.0", "x86_64"), - resource.TestCheckResourceAttr(resourceBasic, "supported_placement_strategies.#", "3"), - resource.TestCheckResourceAttr(resourceBasic, "supported_placement_strategies.0", "cluster"), - resource.TestCheckResourceAttr(resourceBasic, "supported_placement_strategies.1", "partition"), - resource.TestCheckResourceAttr(resourceBasic, "supported_placement_strategies.2", "spread"), - resource.TestCheckResourceAttr(resourceBasic, "supported_root_device_types.#", "1"), - resource.TestCheckResourceAttr(resourceBasic, "supported_root_device_types.0", "ebs"), - resource.TestCheckResourceAttr(resourceBasic, "supported_usages_classes.#", "2"), - resource.TestCheckResourceAttr(resourceBasic, "supported_usages_classes.0", "on-demand"), - resource.TestCheckResourceAttr(resourceBasic, "supported_usages_classes.1", "spot"), - resource.TestCheckResourceAttr(resourceBasic, "supported_virtualization_types.#", "1"), - resource.TestCheckResourceAttr(resourceBasic, "supported_virtualization_types.0", "hvm"), - resource.TestCheckResourceAttr(resourceBasic, "sustained_clock_speed", "3.1"), - resource.TestCheckResourceAttr(resourceBasic, "valid_cores.#", "1"), - resource.TestCheckResourceAttr(resourceBasic, "valid_cores.0", "1"), - resource.TestCheckResourceAttr(resourceBasic, "valid_threads_per_core.#", "2"), - resource.TestCheckResourceAttr(resourceBasic, "valid_threads_per_core.0", "1"), - resource.TestCheckResourceAttr(resourceBasic, "valid_threads_per_core.1", "2"), + resource.TestCheckResourceAttr(dataSourceName, "auto_recovery_supported", "true"), + resource.TestCheckResourceAttr(dataSourceName, "bare_metal", "false"), + resource.TestCheckResourceAttr(dataSourceName, "burstable_performance_supported", "false"), + resource.TestCheckResourceAttr(dataSourceName, "current_generation", "true"), + resource.TestCheckResourceAttr(dataSourceName, "dedicated_hosts_supported", "true"), + resource.TestCheckResourceAttr(dataSourceName, "default_cores", "1"), + resource.TestCheckResourceAttr(dataSourceName, "default_threads_per_core", "2"), + resource.TestCheckResourceAttr(dataSourceName, "default_vcpus", "2"), + resource.TestCheckResourceAttr(dataSourceName, "ebs_encryption_support", "supported"), + resource.TestCheckResourceAttr(dataSourceName, "ebs_nvme_support", "required"), + resource.TestCheckResourceAttr(dataSourceName, "ebs_optimized_support", "default"), + resource.TestCheckResourceAttr(dataSourceName, "efa_supported", "false"), + resource.TestCheckResourceAttr(dataSourceName, "ena_support", "required"), + resource.TestCheckResourceAttr(dataSourceName, "encryption_in_transit_supported", "false"), + resource.TestCheckResourceAttr(dataSourceName, "free_tier_eligible", "false"), + resource.TestCheckResourceAttr(dataSourceName, "hibernation_supported", "true"), + resource.TestCheckResourceAttr(dataSourceName, "hypervisor", "nitro"), + resource.TestCheckResourceAttr(dataSourceName, "instance_storage_supported", "false"), + resource.TestCheckResourceAttr(dataSourceName, "instance_type", "m5.large"), + resource.TestCheckResourceAttr(dataSourceName, "ipv6_supported", "true"), + resource.TestCheckResourceAttr(dataSourceName, "maximum_ipv4_addresses_per_interface", "10"), + resource.TestCheckResourceAttr(dataSourceName, "maximum_ipv6_addresses_per_interface", "10"), + resource.TestCheckResourceAttr(dataSourceName, "maximum_network_interfaces", "3"), + resource.TestCheckResourceAttr(dataSourceName, "memory_size", "8192"), + resource.TestCheckResourceAttr(dataSourceName, "network_performance", "Up to 10 Gigabit"), + resource.TestCheckResourceAttr(dataSourceName, "supported_architectures.#", "1"), + resource.TestCheckResourceAttr(dataSourceName, "supported_architectures.0", "x86_64"), + resource.TestCheckResourceAttr(dataSourceName, "supported_placement_strategies.#", "3"), + resource.TestCheckResourceAttr(dataSourceName, "supported_placement_strategies.0", "cluster"), + resource.TestCheckResourceAttr(dataSourceName, "supported_placement_strategies.1", "partition"), + resource.TestCheckResourceAttr(dataSourceName, "supported_placement_strategies.2", "spread"), + resource.TestCheckResourceAttr(dataSourceName, "supported_root_device_types.#", "1"), + resource.TestCheckResourceAttr(dataSourceName, "supported_root_device_types.0", "ebs"), + resource.TestCheckResourceAttr(dataSourceName, "supported_usages_classes.#", "2"), + resource.TestCheckResourceAttr(dataSourceName, "supported_usages_classes.0", "on-demand"), + resource.TestCheckResourceAttr(dataSourceName, "supported_usages_classes.1", "spot"), + resource.TestCheckResourceAttr(dataSourceName, "supported_virtualization_types.#", "1"), + resource.TestCheckResourceAttr(dataSourceName, "supported_virtualization_types.0", "hvm"), + resource.TestCheckResourceAttr(dataSourceName, "sustained_clock_speed", "3.1"), + resource.TestCheckResourceAttr(dataSourceName, "valid_cores.#", "1"), + resource.TestCheckResourceAttr(dataSourceName, "valid_cores.0", "1"), + resource.TestCheckResourceAttr(dataSourceName, "valid_threads_per_core.#", "2"), + resource.TestCheckResourceAttr(dataSourceName, "valid_threads_per_core.0", "1"), + resource.TestCheckResourceAttr(dataSourceName, "valid_threads_per_core.1", "2"), ), }, }, @@ -69,7 +70,8 @@ func TestAccEC2InstanceTypeDataSource_basic(t *testing.T) { } func TestAccEC2InstanceTypeDataSource_metal(t *testing.T) { - resourceMetal := "data.aws_ec2_instance_type.metal" + dataSourceName := "data.aws_ec2_instance_type.test" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), @@ -78,17 +80,17 @@ func TestAccEC2InstanceTypeDataSource_metal(t *testing.T) { { Config: testAccInstanceTypeDataSourceConfig_metal, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceMetal, "ebs_performance_baseline_bandwidth", "19000"), - resource.TestCheckResourceAttr(resourceMetal, "ebs_performance_baseline_throughput", "2375"), - resource.TestCheckResourceAttr(resourceMetal, "ebs_performance_baseline_iops", "80000"), - resource.TestCheckResourceAttr(resourceMetal, "ebs_performance_maximum_bandwidth", "19000"), - resource.TestCheckResourceAttr(resourceMetal, "ebs_performance_maximum_throughput", "2375"), - resource.TestCheckResourceAttr(resourceMetal, "ebs_performance_maximum_iops", "80000"), - resource.TestCheckResourceAttr(resourceMetal, "instance_disks.#", "1"), - resource.TestCheckResourceAttr(resourceMetal, "instance_disks.0.count", "8"), - resource.TestCheckResourceAttr(resourceMetal, "instance_disks.0.size", "7500"), - resource.TestCheckResourceAttr(resourceMetal, "instance_disks.0.type", "ssd"), - resource.TestCheckResourceAttr(resourceMetal, "total_instance_storage", "60000"), + resource.TestCheckResourceAttr(dataSourceName, "ebs_performance_baseline_bandwidth", "19000"), + resource.TestCheckResourceAttr(dataSourceName, "ebs_performance_baseline_throughput", "2375"), + resource.TestCheckResourceAttr(dataSourceName, "ebs_performance_baseline_iops", "80000"), + resource.TestCheckResourceAttr(dataSourceName, "ebs_performance_maximum_bandwidth", "19000"), + resource.TestCheckResourceAttr(dataSourceName, "ebs_performance_maximum_throughput", "2375"), + resource.TestCheckResourceAttr(dataSourceName, "ebs_performance_maximum_iops", "80000"), + resource.TestCheckResourceAttr(dataSourceName, "instance_disks.#", "1"), + resource.TestCheckResourceAttr(dataSourceName, "instance_disks.0.count", "8"), + resource.TestCheckResourceAttr(dataSourceName, "instance_disks.0.size", "7500"), + resource.TestCheckResourceAttr(dataSourceName, "instance_disks.0.type", "ssd"), + resource.TestCheckResourceAttr(dataSourceName, "total_instance_storage", "60000"), ), }, }, @@ -96,7 +98,8 @@ func TestAccEC2InstanceTypeDataSource_metal(t *testing.T) { } func TestAccEC2InstanceTypeDataSource_gpu(t *testing.T) { - resourceGpu := "data.aws_ec2_instance_type.gpu" + dataSourceName := "data.aws_ec2_instance_type.test" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), @@ -105,11 +108,11 @@ func TestAccEC2InstanceTypeDataSource_gpu(t *testing.T) { { Config: testAccInstanceTypeDataSourceConfig_gpu, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceGpu, "gpus.#", "1"), - resource.TestCheckResourceAttr(resourceGpu, "gpus.0.count", "1"), - resource.TestCheckResourceAttr(resourceGpu, "gpus.0.manufacturer", "NVIDIA"), - resource.TestCheckResourceAttr(resourceGpu, "gpus.0.memory_size", "8192"), - resource.TestCheckResourceAttr(resourceGpu, "gpus.0.name", "M60"), + resource.TestCheckResourceAttr(dataSourceName, "gpus.#", "1"), + resource.TestCheckResourceAttr(dataSourceName, "gpus.0.count", "1"), + resource.TestCheckResourceAttr(dataSourceName, "gpus.0.manufacturer", "NVIDIA"), + resource.TestCheckResourceAttr(dataSourceName, "gpus.0.memory_size", "8192"), + resource.TestCheckResourceAttr(dataSourceName, "gpus.0.name", "M60"), ), }, }, @@ -117,7 +120,8 @@ func TestAccEC2InstanceTypeDataSource_gpu(t *testing.T) { } func TestAccEC2InstanceTypeDataSource_fpga(t *testing.T) { - resourceFpga := "data.aws_ec2_instance_type.fpga" + dataSourceName := "data.aws_ec2_instance_type.test" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), @@ -126,12 +130,12 @@ func TestAccEC2InstanceTypeDataSource_fpga(t *testing.T) { { Config: testAccInstanceTypeDataSourceConfig_fgpa, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceFpga, "fpgas.#", "1"), - resource.TestCheckResourceAttr(resourceFpga, "fpgas.0.count", "1"), - resource.TestCheckResourceAttr(resourceFpga, "fpgas.0.manufacturer", "Xilinx"), - resource.TestCheckResourceAttr(resourceFpga, "fpgas.0.memory_size", "65536"), - resource.TestCheckResourceAttr(resourceFpga, "fpgas.0.name", "Virtex UltraScale (VU9P)"), - resource.TestCheckResourceAttr(resourceFpga, "total_fpga_memory", "65536"), + resource.TestCheckResourceAttr(dataSourceName, "fpgas.#", "1"), + resource.TestCheckResourceAttr(dataSourceName, "fpgas.0.count", "1"), + resource.TestCheckResourceAttr(dataSourceName, "fpgas.0.manufacturer", "Xilinx"), + resource.TestCheckResourceAttr(dataSourceName, "fpgas.0.memory_size", "65536"), + resource.TestCheckResourceAttr(dataSourceName, "fpgas.0.name", "Virtex UltraScale (VU9P)"), + resource.TestCheckResourceAttr(dataSourceName, "total_fpga_memory", "65536"), ), }, }, @@ -139,25 +143,25 @@ func TestAccEC2InstanceTypeDataSource_fpga(t *testing.T) { } const testAccInstanceTypeDataSourceConfig_basic = ` -data "aws_ec2_instance_type" "basic" { +data "aws_ec2_instance_type" "test" { instance_type = "m5.large" } ` const testAccInstanceTypeDataSourceConfig_metal = ` -data "aws_ec2_instance_type" "metal" { +data "aws_ec2_instance_type" "test" { instance_type = "i3en.metal" } ` const testAccInstanceTypeDataSourceConfig_gpu = ` -data "aws_ec2_instance_type" "gpu" { +data "aws_ec2_instance_type" "test" { instance_type = "g3.4xlarge" } ` const testAccInstanceTypeDataSourceConfig_fgpa = ` -data "aws_ec2_instance_type" "fpga" { +data "aws_ec2_instance_type" "test" { instance_type = "f1.2xlarge" } ` diff --git a/internal/service/ec2/ec2_instance_type_offering_data_source.go b/internal/service/ec2/ec2_instance_type_offering_data_source.go index 2d540d82c96..e4c43c96aba 100644 --- a/internal/service/ec2/ec2_instance_type_offering_data_source.go +++ b/internal/service/ec2/ec2_instance_type_offering_data_source.go @@ -21,13 +21,9 @@ func DataSourceInstanceTypeOffering() *schema.Resource { Computed: true, }, "location_type": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - ec2.LocationTypeAvailabilityZone, - ec2.LocationTypeAvailabilityZoneId, - ec2.LocationTypeRegion, - }, false), + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(ec2.LocationType_Values(), false), }, "preferred_instance_types": { Type: schema.TypeList, @@ -51,60 +47,40 @@ func dataSourceInstanceTypeOfferingRead(d *schema.ResourceData, meta interface{} input.LocationType = aws.String(v.(string)) } - var foundInstanceTypes []string - - for { - output, err := conn.DescribeInstanceTypeOfferings(input) - - if err != nil { - return fmt.Errorf("error reading EC2 Instance Type Offerings: %w", err) - } - - if output == nil { - break - } - - for _, instanceTypeOffering := range output.InstanceTypeOfferings { - if instanceTypeOffering == nil { - continue - } - - foundInstanceTypes = append(foundInstanceTypes, aws.StringValue(instanceTypeOffering.InstanceType)) - } - - if aws.StringValue(output.NextToken) == "" { - break - } + instanceTypeOfferings, err := FindInstanceTypeOfferings(conn, input) - input.NextToken = output.NextToken + if err != nil { + return fmt.Errorf("reading EC2 Instance Type Offerings: %w", err) } - if len(foundInstanceTypes) == 0 { + if len(instanceTypeOfferings) == 0 { return fmt.Errorf("no EC2 Instance Type Offerings found matching criteria; try different search") } + var foundInstanceTypes []string + + for _, instanceTypeOffering := range instanceTypeOfferings { + foundInstanceTypes = append(foundInstanceTypes, aws.StringValue(instanceTypeOffering.InstanceType)) + } + var resultInstanceType string // Search preferred instance types in their given order and set result // instance type for first match found - if l := d.Get("preferred_instance_types").([]interface{}); len(l) > 0 { - for _, elem := range l { - preferredInstanceType, ok := elem.(string) - - if !ok { - continue - } + if v, ok := d.GetOk("preferred_instance_types"); ok { + for _, v := range v.([]interface{}) { + if v, ok := v.(string); ok { + for _, foundInstanceType := range foundInstanceTypes { + if foundInstanceType == v { + resultInstanceType = v + break + } + } - for _, foundInstanceType := range foundInstanceTypes { - if foundInstanceType == preferredInstanceType { - resultInstanceType = preferredInstanceType + if resultInstanceType != "" { break } } - - if resultInstanceType != "" { - break - } } } @@ -120,9 +96,8 @@ func dataSourceInstanceTypeOfferingRead(d *schema.ResourceData, meta interface{} return fmt.Errorf("no EC2 Instance Type Offerings found matching criteria; try different search") } - d.Set("instance_type", resultInstanceType) - d.SetId(resultInstanceType) + d.Set("instance_type", resultInstanceType) return nil } diff --git a/internal/service/ec2/ec2_instance_type_offering_data_source_test.go b/internal/service/ec2/ec2_instance_type_offering_data_source_test.go index 7b2e3c82cfc..5cfbd1bee50 100644 --- a/internal/service/ec2/ec2_instance_type_offering_data_source_test.go +++ b/internal/service/ec2/ec2_instance_type_offering_data_source_test.go @@ -3,18 +3,16 @@ package ec2_test import ( "testing" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-provider-aws/internal/acctest" - "github.com/hashicorp/terraform-provider-aws/internal/conns" ) func TestAccEC2InstanceTypeOfferingDataSource_filter(t *testing.T) { dataSourceName := "data.aws_ec2_instance_type_offering.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t); testAccPreCheckInstanceTypeOffering(t) }, + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckInstanceTypeOfferings(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, CheckDestroy: nil, @@ -33,7 +31,7 @@ func TestAccEC2InstanceTypeOfferingDataSource_locationType(t *testing.T) { dataSourceName := "data.aws_ec2_instance_type_offering.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t); testAccPreCheckInstanceTypeOffering(t) }, + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckInstanceTypeOfferings(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, CheckDestroy: nil, @@ -52,7 +50,7 @@ func TestAccEC2InstanceTypeOfferingDataSource_preferredInstanceTypes(t *testing. dataSourceName := "data.aws_ec2_instance_type_offering.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t); testAccPreCheckInstanceTypeOffering(t) }, + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckInstanceTypeOfferings(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, CheckDestroy: nil, @@ -67,24 +65,6 @@ func TestAccEC2InstanceTypeOfferingDataSource_preferredInstanceTypes(t *testing. }) } -func testAccPreCheckInstanceTypeOffering(t *testing.T) { - conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - - input := &ec2.DescribeInstanceTypeOfferingsInput{ - MaxResults: aws.Int64(5), - } - - _, err := conn.DescribeInstanceTypeOfferings(input) - - if acctest.PreCheckSkipError(err) { - t.Skipf("skipping acceptance testing: %s", err) - } - - if err != nil { - t.Fatalf("unexpected PreCheck error: %s", err) - } -} - func testAccInstanceTypeOfferingDataSourceConfig_filter() string { return ` # Rather than hardcode an instance type in the testing, @@ -101,7 +81,7 @@ data "aws_ec2_instance_type_offering" "test" { } func testAccInstanceTypeOfferingDataSourceConfig_location() string { - return acctest.ConfigAvailableAZsNoOptIn() + ` + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), ` # Rather than hardcode an instance type in the testing, # use the first result from all available offerings. data "aws_ec2_instance_type_offerings" "test" { @@ -126,7 +106,7 @@ data "aws_ec2_instance_type_offering" "test" { location_type = "availability-zone" } -` +`) } func testAccInstanceTypeOfferingDataSourceConfig_preferreds() string { diff --git a/internal/service/ec2/ec2_instance_type_offerings_data_source.go b/internal/service/ec2/ec2_instance_type_offerings_data_source.go index b6b36bb4129..d2eababc3a0 100644 --- a/internal/service/ec2/ec2_instance_type_offerings_data_source.go +++ b/internal/service/ec2/ec2_instance_type_offerings_data_source.go @@ -57,39 +57,22 @@ func dataSourceInstanceTypeOfferingsRead(d *schema.ResourceData, meta interface{ var locations []string var locationTypes []string - err := conn.DescribeInstanceTypeOfferingsPages(input, func(page *ec2.DescribeInstanceTypeOfferingsOutput, lastPage bool) bool { - if page == nil { - return !lastPage - } - - for _, instanceTypeOffering := range page.InstanceTypeOfferings { - if instanceTypeOffering == nil { - continue - } - - instanceTypes = append(instanceTypes, aws.StringValue(instanceTypeOffering.InstanceType)) - locations = append(locations, aws.StringValue(instanceTypeOffering.Location)) - locationTypes = append(locationTypes, aws.StringValue(instanceTypeOffering.LocationType)) - } - - return !lastPage - }) + instanceTypeOfferings, err := FindInstanceTypeOfferings(conn, input) if err != nil { - return fmt.Errorf("error reading EC2 Instance Type Offerings: %w", err) + return fmt.Errorf("reading EC2 Instance Type Offerings: %w", err) } - if err := d.Set("instance_types", instanceTypes); err != nil { - return fmt.Errorf("error setting instance_types: %w", err) - } - if err := d.Set("locations", locations); err != nil { - return fmt.Errorf("error setting locations: %w", err) - } - if err := d.Set("location_types", locationTypes); err != nil { - return fmt.Errorf("error setting location_types: %w", err) + for _, instanceTypeOffering := range instanceTypeOfferings { + instanceTypes = append(instanceTypes, aws.StringValue(instanceTypeOffering.InstanceType)) + locations = append(locations, aws.StringValue(instanceTypeOffering.Location)) + locationTypes = append(locationTypes, aws.StringValue(instanceTypeOffering.LocationType)) } d.SetId(meta.(*conns.AWSClient).Region) + d.Set("instance_types", instanceTypes) + d.Set("locations", locations) + d.Set("location_types", locationTypes) return nil } diff --git a/internal/service/ec2/ec2_instance_type_offerings_data_source_test.go b/internal/service/ec2/ec2_instance_type_offerings_data_source_test.go index ac92ef09dde..050156c3cfb 100644 --- a/internal/service/ec2/ec2_instance_type_offerings_data_source_test.go +++ b/internal/service/ec2/ec2_instance_type_offerings_data_source_test.go @@ -1,13 +1,11 @@ package ec2_test import ( - "fmt" "testing" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" ) @@ -24,7 +22,9 @@ func TestAccEC2InstanceTypeOfferingsDataSource_filter(t *testing.T) { { Config: testAccInstanceTypeOfferingsDataSourceConfig_filter(), Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceTypeOfferingsInstanceTypes(dataSourceName), + acctest.CheckResourceAttrGreaterThanValue(dataSourceName, "instance_types.#", "0"), + acctest.CheckResourceAttrGreaterThanValue(dataSourceName, "locations.#", "0"), + acctest.CheckResourceAttrGreaterThanValue(dataSourceName, "location_types.#", "0"), ), }, }, @@ -43,52 +43,15 @@ func TestAccEC2InstanceTypeOfferingsDataSource_locationType(t *testing.T) { { Config: testAccInstanceTypeOfferingsDataSourceConfig_location(), Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceTypeOfferingsInstanceTypes(dataSourceName), - testAccCheckInstanceTypeOfferingsLocations(dataSourceName), + acctest.CheckResourceAttrGreaterThanValue(dataSourceName, "instance_types.#", "0"), + acctest.CheckResourceAttrGreaterThanValue(dataSourceName, "locations.#", "0"), + acctest.CheckResourceAttrGreaterThanValue(dataSourceName, "location_types.#", "0"), ), }, }, }) } -func testAccCheckInstanceTypeOfferingsInstanceTypes(dataSourceName string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[dataSourceName] - if !ok { - return fmt.Errorf("Not found: %s", dataSourceName) - } - - if v := rs.Primary.Attributes["instance_types.#"]; v == "0" { - return fmt.Errorf("expected at least one instance_types result, got none") - } - - if v := rs.Primary.Attributes["locations.#"]; v == "0" { - return fmt.Errorf("expected at least one locations result, got none") - } - - if v := rs.Primary.Attributes["location_types.#"]; v == "0" { - return fmt.Errorf("expected at least one location_types result, got none") - } - - return nil - } -} - -func testAccCheckInstanceTypeOfferingsLocations(dataSourceName string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[dataSourceName] - if !ok { - return fmt.Errorf("Not found: %s", dataSourceName) - } - - if v := rs.Primary.Attributes["locations.#"]; v == "0" { - return fmt.Errorf("expected at least one locations result, got none") - } - - return nil - } -} - func testAccPreCheckInstanceTypeOfferings(t *testing.T) { conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn diff --git a/internal/service/ec2/ec2_instance_types_data_source.go b/internal/service/ec2/ec2_instance_types_data_source.go index 7a8d97ea40f..e9ca20c4457 100644 --- a/internal/service/ec2/ec2_instance_types_data_source.go +++ b/internal/service/ec2/ec2_instance_types_data_source.go @@ -33,26 +33,16 @@ func dataSourceInstanceTypesRead(d *schema.ResourceData, meta interface{}) error input.Filters = BuildFiltersDataSource(v.(*schema.Set)) } - var instanceTypes []string - - err := conn.DescribeInstanceTypesPages(input, func(page *ec2.DescribeInstanceTypesOutput, lastPage bool) bool { - if page == nil { - return !lastPage - } + output, err := FindInstanceTypes(conn, input) - for _, instanceType := range page.InstanceTypes { - if instanceType == nil { - continue - } - - instanceTypes = append(instanceTypes, aws.StringValue(instanceType.InstanceType)) - } + if err != nil { + return fmt.Errorf("reading EC2 Instance Types: %w", err) + } - return !lastPage - }) + var instanceTypes []string - if err != nil { - return fmt.Errorf("error listing EC2 Instance Types: %w", err) + for _, instanceType := range output { + instanceTypes = append(instanceTypes, aws.StringValue(instanceType.InstanceType)) } d.SetId(meta.(*conns.AWSClient).Region) diff --git a/internal/service/ec2/ec2_launch_template.go b/internal/service/ec2/ec2_launch_template.go index ed1a04ae59e..aa4cc37b5c6 100644 --- a/internal/service/ec2/ec2_launch_template.go +++ b/internal/service/ec2/ec2_launch_template.go @@ -5,7 +5,6 @@ import ( "fmt" "log" "strconv" - "strings" "time" "github.com/aws/aws-sdk-go/aws" @@ -961,7 +960,6 @@ func resourceLaunchTemplateCreate(d *schema.ResourceData, meta interface{}) erro input := &ec2.CreateLaunchTemplateInput{ ClientToken: aws.String(resource.UniqueId()), LaunchTemplateName: aws.String(name), - LaunchTemplateData: expandRequestLaunchTemplateData(d), TagSpecifications: tagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeLaunchTemplate), } @@ -969,6 +967,12 @@ func resourceLaunchTemplateCreate(d *schema.ResourceData, meta interface{}) erro input.VersionDescription = aws.String(v.(string)) } + if v, err := expandRequestLaunchTemplateData(conn, d); err == nil { + input.LaunchTemplateData = v + } else { + return err + } + log.Printf("[DEBUG] Creating EC2 Launch Template: %s", input) output, err := conn.CreateLaunchTemplate(input) @@ -1019,7 +1023,7 @@ func resourceLaunchTemplateRead(d *schema.ResourceData, meta interface{}) error d.Set("name", lt.LaunchTemplateName) d.Set("name_prefix", create.NamePrefixFromName(aws.StringValue(lt.LaunchTemplateName))) - if err := flattenResponseLaunchTemplateData(d, ltv.LaunchTemplateData); err != nil { + if err := flattenResponseLaunchTemplateData(conn, d, ltv.LaunchTemplateData); err != nil { return err } @@ -1077,15 +1081,20 @@ func resourceLaunchTemplateUpdate(d *schema.ResourceData, meta interface{}) erro if d.HasChanges(updateKeys...) { input := &ec2.CreateLaunchTemplateVersionInput{ - ClientToken: aws.String(resource.UniqueId()), - LaunchTemplateData: expandRequestLaunchTemplateData(d), - LaunchTemplateId: aws.String(d.Id()), + ClientToken: aws.String(resource.UniqueId()), + LaunchTemplateId: aws.String(d.Id()), } if v, ok := d.GetOk("description"); ok { input.VersionDescription = aws.String(v.(string)) } + if v, err := expandRequestLaunchTemplateData(conn, d); err == nil { + input.LaunchTemplateData = v + } else { + return err + } + output, err := conn.CreateLaunchTemplateVersion(input) if err != nil { @@ -1144,7 +1153,7 @@ func resourceLaunchTemplateDelete(d *schema.ResourceData, meta interface{}) erro return nil } -func expandRequestLaunchTemplateData(d *schema.ResourceData) *ec2.RequestLaunchTemplateData { +func expandRequestLaunchTemplateData(conn *ec2.EC2, d *schema.ResourceData) (*ec2.RequestLaunchTemplateData, error) { apiObject := &ec2.RequestLaunchTemplateData{ // Always set at least one field. UserData: aws.String(d.Get("user_data").(string)), @@ -1170,8 +1179,18 @@ func expandRequestLaunchTemplateData(d *schema.ResourceData) *ec2.RequestLaunchT apiObject.CpuOptions = expandLaunchTemplateCPUOptionsRequest(v.([]interface{})[0].(map[string]interface{})) } - if v, ok := d.GetOk("credit_specification"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil && (strings.HasPrefix(instanceType, "t2") || strings.HasPrefix(instanceType, "t3")) { - apiObject.CreditSpecification = expandCreditSpecificationRequest(v.([]interface{})[0].(map[string]interface{})) + if v, ok := d.GetOk("credit_specification"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + if instanceType != "" { + instanceTypeInfo, err := FindInstanceTypeByName(conn, instanceType) + + if err != nil { + return nil, fmt.Errorf("reading EC2 Instance Type (%s): %w", instanceType, err) + } + + if aws.BoolValue(instanceTypeInfo.BurstablePerformanceSupported) { + apiObject.CreditSpecification = expandCreditSpecificationRequest(v.([]interface{})[0].(map[string]interface{})) + } + } } if v, ok := d.GetOk("disable_api_stop"); ok { @@ -1288,7 +1307,7 @@ func expandRequestLaunchTemplateData(d *schema.ResourceData) *ec2.RequestLaunchT apiObject.SecurityGroupIds = flex.ExpandStringSet(v.(*schema.Set)) } - return apiObject + return apiObject, nil } func expandLaunchTemplateBlockDeviceMappingRequest(tfMap map[string]interface{}) *ec2.LaunchTemplateBlockDeviceMappingRequest { @@ -2151,7 +2170,9 @@ func expandLaunchTemplateTagSpecificationRequests(tfList []interface{}) []*ec2.L return apiObjects } -func flattenResponseLaunchTemplateData(d *schema.ResourceData, apiObject *ec2.ResponseLaunchTemplateData) error { +func flattenResponseLaunchTemplateData(conn *ec2.EC2, d *schema.ResourceData, apiObject *ec2.ResponseLaunchTemplateData) error { + instanceType := aws.StringValue(apiObject.InstanceType) + if err := d.Set("block_device_mappings", flattenLaunchTemplateBlockDeviceMappings(apiObject.BlockDeviceMappings)); err != nil { return fmt.Errorf("error setting block_device_mappings: %w", err) } @@ -2169,9 +2190,17 @@ func flattenResponseLaunchTemplateData(d *schema.ResourceData, apiObject *ec2.Re } else { d.Set("cpu_options", nil) } - if apiObject.CreditSpecification != nil && (strings.HasPrefix(aws.StringValue(apiObject.InstanceType), "t2") || strings.HasPrefix(aws.StringValue(apiObject.InstanceType), "t3")) { - if err := d.Set("credit_specification", []interface{}{flattenCreditSpecification(apiObject.CreditSpecification)}); err != nil { - return fmt.Errorf("error setting credit_specification: %w", err) + if apiObject.CreditSpecification != nil && instanceType != "" { + instanceTypeInfo, err := FindInstanceTypeByName(conn, instanceType) + + if err != nil { + return fmt.Errorf("reading EC2 Instance Type (%s): %w", instanceType, err) + } + + if aws.BoolValue(instanceTypeInfo.BurstablePerformanceSupported) { + if err := d.Set("credit_specification", []interface{}{flattenCreditSpecification(apiObject.CreditSpecification)}); err != nil { + return fmt.Errorf("error setting credit_specification: %w", err) + } } } // Don't overwrite any configured value. d.Set("disable_api_stop", apiObject.DisableApiStop) @@ -2232,7 +2261,7 @@ func flattenResponseLaunchTemplateData(d *schema.ResourceData, apiObject *ec2.Re } else { d.Set("instance_requirements", nil) } - d.Set("instance_type", apiObject.InstanceType) + d.Set("instance_type", instanceType) d.Set("kernel_id", apiObject.KernelId) d.Set("key_name", apiObject.KeyName) if err := d.Set("license_specification", flattenLaunchTemplateLicenseConfigurations(apiObject.LicenseSpecifications)); err != nil { diff --git a/internal/service/ec2/ec2_launch_template_data_source.go b/internal/service/ec2/ec2_launch_template_data_source.go index d368bfe247d..5c0c7949b4e 100644 --- a/internal/service/ec2/ec2_launch_template_data_source.go +++ b/internal/service/ec2/ec2_launch_template_data_source.go @@ -795,7 +795,7 @@ func dataSourceLaunchTemplateRead(d *schema.ResourceData, meta interface{}) erro d.Set("latest_version", lt.LatestVersionNumber) d.Set("name", lt.LaunchTemplateName) - if err := flattenResponseLaunchTemplateData(d, ltv.LaunchTemplateData); err != nil { + if err := flattenResponseLaunchTemplateData(conn, d, ltv.LaunchTemplateData); err != nil { return err } diff --git a/internal/service/ec2/ec2_launch_template_test.go b/internal/service/ec2/ec2_launch_template_test.go index efb24780349..ac3c607a62b 100644 --- a/internal/service/ec2/ec2_launch_template_test.go +++ b/internal/service/ec2/ec2_launch_template_test.go @@ -741,6 +741,34 @@ func TestAccEC2LaunchTemplate_CreditSpecification_t3(t *testing.T) { }) } +func TestAccEC2LaunchTemplate_CreditSpecification_t4g(t *testing.T) { + var template ec2.LaunchTemplate + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_launch_template.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckLaunchTemplateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccLaunchTemplateConfig_creditSpecification(rName, "t4g.micro", "unlimited"), + Check: resource.ComposeTestCheckFunc( + testAccCheckLaunchTemplateExists(resourceName, &template), + resource.TestCheckResourceAttr(resourceName, "credit_specification.#", "1"), + resource.TestCheckResourceAttr(resourceName, "credit_specification.0.cpu_credits", "unlimited"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/6757 func TestAccEC2LaunchTemplate_IAMInstanceProfile_emptyBlock(t *testing.T) { var template1 ec2.LaunchTemplate diff --git a/internal/service/ec2/filter.go b/internal/service/ec2/filter.go index 60ca4a2b682..3497a706640 100644 --- a/internal/service/ec2/filter.go +++ b/internal/service/ec2/filter.go @@ -28,28 +28,32 @@ import ( // for the "Filters" attribute on most of the "Describe..." API functions in // the EC2 API, to aid in the implementation of Terraform data sources that // retrieve data about EC2 objects. -func BuildAttributeFilterList(attrs map[string]string) []*ec2.Filter { +func BuildAttributeFilterList(m map[string]string) []*ec2.Filter { var filters []*ec2.Filter // sort the filters by name to make the output deterministic var names []string - for filterName := range attrs { - names = append(names, filterName) + for k := range m { + names = append(names, k) } sort.Strings(names) - for _, filterName := range names { - value := attrs[filterName] + for _, name := range names { + value := m[name] if value == "" { continue } - filters = append(filters, &ec2.Filter{ - Name: aws.String(filterName), - Values: []*string{aws.String(value)}, - }) + filters = append(filters, NewFilter(name, []string{value})) } return filters } + +func NewFilter(name string, values []string) *ec2.Filter { + return &ec2.Filter{ + Name: aws.String(name), + Values: aws.StringSlice(values), + } +} diff --git a/internal/service/ec2/find.go b/internal/service/ec2/find.go index c8b472e7ec0..33dd7b27707 100644 --- a/internal/service/ec2/find.go +++ b/internal/service/ec2/find.go @@ -987,6 +987,86 @@ func FindInstanceCreditSpecificationByID(conn *ec2.EC2, id string) (*ec2.Instanc return output, nil } +func FindInstanceTypes(conn *ec2.EC2, input *ec2.DescribeInstanceTypesInput) ([]*ec2.InstanceTypeInfo, error) { + var output []*ec2.InstanceTypeInfo + + err := conn.DescribeInstanceTypesPages(input, func(page *ec2.DescribeInstanceTypesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.InstanceTypes { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindInstanceType(conn *ec2.EC2, input *ec2.DescribeInstanceTypesInput) (*ec2.InstanceTypeInfo, error) { + output, err := FindInstanceTypes(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindInstanceTypeByName(conn *ec2.EC2, name string) (*ec2.InstanceTypeInfo, error) { + input := &ec2.DescribeInstanceTypesInput{ + InstanceTypes: aws.StringSlice([]string{name}), + } + + output, err := FindInstanceType(conn, input) + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindInstanceTypeOfferings(conn *ec2.EC2, input *ec2.DescribeInstanceTypeOfferingsInput) ([]*ec2.InstanceTypeOffering, error) { + var output []*ec2.InstanceTypeOffering + + err := conn.DescribeInstanceTypeOfferingsPages(input, func(page *ec2.DescribeInstanceTypeOfferingsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.InstanceTypeOfferings { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + return output, nil +} + func FindLocalGatewayRouteTables(conn *ec2.EC2, input *ec2.DescribeLocalGatewayRouteTablesInput) ([]*ec2.LocalGatewayRouteTable, error) { var output []*ec2.LocalGatewayRouteTable diff --git a/internal/service/ec2/vpc_default_vpc_dhcp_options.go b/internal/service/ec2/vpc_default_vpc_dhcp_options.go index 3667144ba53..55dd9cebfc8 100644 --- a/internal/service/ec2/vpc_default_vpc_dhcp_options.go +++ b/internal/service/ec2/vpc_default_vpc_dhcp_options.go @@ -2,112 +2,105 @@ package ec2 import ( "fmt" - "log" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/endpoints" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/verify" ) func ResourceDefaultVPCDHCPOptions() *schema.Resource { - // reuse aws_vpc_dhcp_options schema, and methods for READ, UPDATE - dvpc := ResourceVPCDHCPOptions() - dvpc.Create = resourceDefaultVPCDHCPOptionsCreate - dvpc.Delete = resourceDefaultVPCDHCPOptionsDelete - - // domain_name is a computed value for Default Default DHCP Options Sets - dvpc.Schema["domain_name"] = &schema.Schema{ - Type: schema.TypeString, - Computed: true, - } - // domain_name_servers is a computed value for Default Default DHCP Options Sets - dvpc.Schema["domain_name_servers"] = &schema.Schema{ - Type: schema.TypeString, - Computed: true, - } - // ntp_servers is a computed value for Default Default DHCP Options Sets - dvpc.Schema["ntp_servers"] = &schema.Schema{ - Type: schema.TypeString, - Computed: true, - } + //lintignore:R011 + return &schema.Resource{ + Create: resourceDefaultVPCDHCPOptionsCreate, + Read: resourceVPCDHCPOptionsRead, + Update: resourceVPCDHCPOptionsUpdate, + Delete: schema.Noop, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, - dvpc.Schema["owner_id"] = &schema.Schema{ - Type: schema.TypeString, - Computed: true, - Optional: true, + CustomizeDiff: verify.SetTagsDiff, + + // Keep in sync with aws_vpc_dhcp_options' schema with the following changes: + // - domain_name is Computed-only + // - domain_name_servers is Computed-only + // - netbios_name_servers is Computed-only + // - netbios_node_type is Computed-only + // - ntp_servers is Computed-only + // - owner_id is Optional/Computed + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "domain_name": { + Type: schema.TypeString, + Computed: true, + }, + "domain_name_servers": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "netbios_name_servers": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "netbios_node_type": { + Type: schema.TypeString, + Computed: true, + }, + "ntp_servers": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "owner_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + }, } - - return dvpc } func resourceDefaultVPCDHCPOptionsCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - filters := []*ec2.Filter{ - { - Name: aws.String("key"), - Values: aws.StringSlice([]string{"domain-name"}), - }, - { - Name: aws.String("value"), - Values: aws.StringSlice([]string{RegionalPrivateDNSSuffix(meta.(*conns.AWSClient).Region)}), - }, - { - Name: aws.String("key"), - Values: aws.StringSlice([]string{"domain-name-servers"}), - }, - { - Name: aws.String("value"), - Values: aws.StringSlice([]string{"AmazonProvidedDNS"}), - }, - } - - if v, ok := d.GetOk("owner_id"); ok { - filter := &ec2.Filter{ - Name: aws.String("owner-id"), - Values: aws.StringSlice([]string{v.(string)}), - } + input := &ec2.DescribeDhcpOptionsInput{} - filters = append(filters, filter) - } + input.Filters = append(input.Filters, + NewFilter("key", []string{"domain-name"}), + NewFilter("value", []string{RegionalPrivateDNSSuffix(meta.(*conns.AWSClient).Region)}), + NewFilter("key", []string{"domain-name-servers"}), + NewFilter("value", []string{"AmazonProvidedDNS"}), + ) - req := &ec2.DescribeDhcpOptionsInput{ - Filters: filters, + if v, ok := d.GetOk("owner_id"); ok { + input.Filters = append(input.Filters, BuildAttributeFilterList(map[string]string{ + "owner-id": v.(string), + })...) } - var dhcpOptions []*ec2.DhcpOptions - err := conn.DescribeDhcpOptionsPages(req, func(page *ec2.DescribeDhcpOptionsOutput, lastPage bool) bool { - dhcpOptions = append(dhcpOptions, page.DhcpOptions...) - return !lastPage - }) + dhcpOptions, err := FindDHCPOptions(conn, input) if err != nil { - return fmt.Errorf("Error describing DHCP options: %s", err) + return fmt.Errorf("reading EC2 Default DHCP Options Set: %w", err) } - if len(dhcpOptions) == 0 { - return fmt.Errorf("Default DHCP Options Set not found") - } - - if len(dhcpOptions) > 1 { - return fmt.Errorf("Multiple default DHCP Options Sets found") - } - - if dhcpOptions[0] == nil { - return fmt.Errorf("Default DHCP Options Set is empty") - } - d.SetId(aws.StringValue(dhcpOptions[0].DhcpOptionsId)) + d.SetId(aws.StringValue(dhcpOptions.DhcpOptionsId)) return resourceVPCDHCPOptionsUpdate(d, meta) } -func resourceDefaultVPCDHCPOptionsDelete(d *schema.ResourceData, meta interface{}) error { - log.Printf("[WARN] Cannot destroy Default DHCP Options Set. Terraform will remove this resource from the state file, however resources may remain.") - return nil -} - func RegionalPrivateDNSSuffix(region string) string { if region == endpoints.UsEast1RegionID { return "ec2.internal" diff --git a/internal/service/ec2/vpc_default_vpc_dhcp_options_test.go b/internal/service/ec2/vpc_default_vpc_dhcp_options_test.go index 0287f8875af..ab69d3ac4a0 100644 --- a/internal/service/ec2/vpc_default_vpc_dhcp_options_test.go +++ b/internal/service/ec2/vpc_default_vpc_dhcp_options_test.go @@ -6,20 +6,33 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" ) -func TestAccVPCDefaultVPCDHCPOptions_basic(t *testing.T) { +func TestAccVPCDefaultVPCDHCPOptions_serial(t *testing.T) { + testCases := map[string]func(t *testing.T){ + "basic": testAccDefaultVPCDHCPOptions_basic, + "owner": testAccDefaultVPCDHCPOptions_owner, + } + + for name, tc := range testCases { + tc := tc + t.Run(name, func(t *testing.T) { + tc(t) + }) + } +} + +func testAccDefaultVPCDHCPOptions_basic(t *testing.T) { var d ec2.DhcpOptions resourceName := "aws_default_vpc_dhcp_options.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckDefaultVPCDHCPOptionsDestroy, + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccVPCDefaultVPCDHCPOptionsConfig_basic, @@ -27,25 +40,26 @@ func TestAccVPCDefaultVPCDHCPOptions_basic(t *testing.T) { testAccCheckDHCPOptionsExists(resourceName, &d), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`dhcp-options/dopt-.+`)), resource.TestCheckResourceAttr(resourceName, "domain_name", tfec2.RegionalPrivateDNSSuffix(acctest.Region())), - resource.TestCheckResourceAttr(resourceName, "domain_name_servers", "AmazonProvidedDNS"), + resource.TestCheckResourceAttr(resourceName, "domain_name_servers.#", "1"), + resource.TestCheckResourceAttr(resourceName, "domain_name_servers.0", "AmazonProvidedDNS"), + acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.Name", "Default DHCP Option Set"), - acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), ), }, }, }) } -func TestAccVPCDefaultVPCDHCPOptions_owner(t *testing.T) { +func testAccDefaultVPCDHCPOptions_owner(t *testing.T) { var d ec2.DhcpOptions resourceName := "aws_default_vpc_dhcp_options.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckDefaultVPCDHCPOptionsDestroy, + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccVPCDefaultVPCDHCPOptionsConfig_owner, @@ -53,21 +67,17 @@ func TestAccVPCDefaultVPCDHCPOptions_owner(t *testing.T) { testAccCheckDHCPOptionsExists(resourceName, &d), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`dhcp-options/dopt-.+`)), resource.TestCheckResourceAttr(resourceName, "domain_name", tfec2.RegionalPrivateDNSSuffix(acctest.Region())), - resource.TestCheckResourceAttr(resourceName, "domain_name_servers", "AmazonProvidedDNS"), + resource.TestCheckResourceAttr(resourceName, "domain_name_servers.#", "1"), + resource.TestCheckResourceAttr(resourceName, "domain_name_servers.0", "AmazonProvidedDNS"), + acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.Name", "Default DHCP Option Set"), - acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), ), }, }, }) } -func testAccCheckDefaultVPCDHCPOptionsDestroy(s *terraform.State) error { - // We expect DHCP Options Set to still exist - return nil -} - const testAccVPCDefaultVPCDHCPOptionsConfig_basic = ` resource "aws_default_vpc_dhcp_options" "test" { tags = { diff --git a/internal/service/ec2/vpc_dhcp_options.go b/internal/service/ec2/vpc_dhcp_options.go index b80403f11c0..02b0b383898 100644 --- a/internal/service/ec2/vpc_dhcp_options.go +++ b/internal/service/ec2/vpc_dhcp_options.go @@ -26,38 +26,45 @@ func ResourceVPCDHCPOptions() *schema.Resource { State: schema.ImportStatePassthrough, }, + // Keep in sync with aws_default_vpc_dhcp_options' schema. + // See notes in vpc_default_vpc_dhcp_options.go. Schema: map[string]*schema.Schema{ "arn": { Type: schema.TypeString, Computed: true, }, "domain_name": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + AtLeastOneOf: []string{"domain_name", "domain_name_servers", "netbios_name_servers", "netbios_node_type", "ntp_servers"}, }, "domain_name_servers": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, - Elem: &schema.Schema{Type: schema.TypeString}, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + AtLeastOneOf: []string{"domain_name", "domain_name_servers", "netbios_name_servers", "netbios_node_type", "ntp_servers"}, }, "netbios_name_servers": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, - Elem: &schema.Schema{Type: schema.TypeString}, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + AtLeastOneOf: []string{"domain_name", "domain_name_servers", "netbios_name_servers", "netbios_node_type", "ntp_servers"}, }, "netbios_node_type": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + AtLeastOneOf: []string{"domain_name", "domain_name_servers", "netbios_name_servers", "netbios_node_type", "ntp_servers"}, }, "ntp_servers": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, - Elem: &schema.Schema{Type: schema.TypeString}, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + AtLeastOneOf: []string{"domain_name", "domain_name_servers", "netbios_name_servers", "netbios_node_type", "ntp_servers"}, }, "owner_id": { Type: schema.TypeString, @@ -246,6 +253,10 @@ func newDHCPOptionsMap(tfToApi map[string]string) *dhcpOptionsMap { // dhcpConfigurationsToResourceData sets Terraform ResourceData from a list of AWS API DHCP configurations. func (m *dhcpOptionsMap) dhcpConfigurationsToResourceData(dhcpConfigurations []*ec2.DhcpConfiguration, d *schema.ResourceData) error { + for v := range m.tfToApi { + d.Set(v, nil) + } + for _, dhcpConfiguration := range dhcpConfigurations { apiName := aws.StringValue(dhcpConfiguration.Key) if tfName, ok := m.apiToTf[apiName]; ok { diff --git a/internal/service/ec2/vpc_dhcp_options_test.go b/internal/service/ec2/vpc_dhcp_options_test.go index 4db79944978..614eb462039 100644 --- a/internal/service/ec2/vpc_dhcp_options_test.go +++ b/internal/service/ec2/vpc_dhcp_options_test.go @@ -18,7 +18,6 @@ import ( func TestAccVPCDHCPOptions_basic(t *testing.T) { var d ec2.DhcpOptions resourceName := "aws_vpc_dhcp_options.test" - rName := sdkacctest.RandString(5) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -27,18 +26,57 @@ func TestAccVPCDHCPOptions_basic(t *testing.T) { CheckDestroy: testAccCheckDHCPOptionsDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCDHCPOptionsConfig_basic(rName), + Config: testAccVPCDHCPOptionsConfig_basic, Check: resource.ComposeTestCheckFunc( testAccCheckDHCPOptionsExists(resourceName, &d), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`dhcp-options/dopt-.+`)), - resource.TestCheckResourceAttr(resourceName, "domain_name", fmt.Sprintf("service.%s", rName)), + resource.TestCheckResourceAttr(resourceName, "domain_name", ""), + resource.TestCheckResourceAttr(resourceName, "domain_name_servers.#", "0"), + resource.TestCheckResourceAttr(resourceName, "netbios_name_servers.#", "0"), + resource.TestCheckResourceAttr(resourceName, "netbios_node_type", "1"), + resource.TestCheckResourceAttr(resourceName, "ntp_servers.#", "0"), + acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccVPCDHCPOptions_full(t *testing.T) { + var d ec2.DhcpOptions + resourceName := "aws_vpc_dhcp_options.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + domainName := acctest.RandomDomainName() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckDHCPOptionsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVPCDHCPOptionsConfig_full(rName, domainName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckDHCPOptionsExists(resourceName, &d), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`dhcp-options/dopt-.+`)), + resource.TestCheckResourceAttr(resourceName, "domain_name", domainName), + resource.TestCheckResourceAttr(resourceName, "domain_name_servers.#", "2"), resource.TestCheckResourceAttr(resourceName, "domain_name_servers.0", "127.0.0.1"), resource.TestCheckResourceAttr(resourceName, "domain_name_servers.1", "10.0.0.2"), - resource.TestCheckResourceAttr(resourceName, "ntp_servers.0", "127.0.0.1"), + resource.TestCheckResourceAttr(resourceName, "netbios_name_servers.#", "1"), resource.TestCheckResourceAttr(resourceName, "netbios_name_servers.0", "127.0.0.1"), resource.TestCheckResourceAttr(resourceName, "netbios_node_type", "2"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "ntp_servers.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ntp_servers.0", "127.0.0.1"), acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), ), }, { @@ -53,7 +91,6 @@ func TestAccVPCDHCPOptions_basic(t *testing.T) { func TestAccVPCDHCPOptions_tags(t *testing.T) { var d ec2.DhcpOptions resourceName := "aws_vpc_dhcp_options.test" - rName := sdkacctest.RandString(5) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -62,7 +99,7 @@ func TestAccVPCDHCPOptions_tags(t *testing.T) { CheckDestroy: testAccCheckDHCPOptionsDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCDHCPOptionsConfig_tags1(rName, "key1", "value1"), + Config: testAccVPCDHCPOptionsConfig_tags1("key1", "value1"), Check: resource.ComposeTestCheckFunc( testAccCheckDHCPOptionsExists(resourceName, &d), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), @@ -75,7 +112,7 @@ func TestAccVPCDHCPOptions_tags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccVPCDHCPOptionsConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), + Config: testAccVPCDHCPOptionsConfig_tags2("key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckDHCPOptionsExists(resourceName, &d), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), @@ -84,7 +121,7 @@ func TestAccVPCDHCPOptions_tags(t *testing.T) { ), }, { - Config: testAccVPCDHCPOptionsConfig_tags1(rName, "key2", "value2"), + Config: testAccVPCDHCPOptionsConfig_tags1("key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckDHCPOptionsExists(resourceName, &d), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), @@ -98,7 +135,6 @@ func TestAccVPCDHCPOptions_tags(t *testing.T) { func TestAccVPCDHCPOptions_disappears(t *testing.T) { var d ec2.DhcpOptions resourceName := "aws_vpc_dhcp_options.test" - rName := sdkacctest.RandString(5) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -107,7 +143,7 @@ func TestAccVPCDHCPOptions_disappears(t *testing.T) { CheckDestroy: testAccCheckDHCPOptionsDestroy, Steps: []resource.TestStep{ { - Config: testAccVPCDHCPOptionsConfig_basic(rName), + Config: testAccVPCDHCPOptionsConfig_basic, Check: resource.ComposeTestCheckFunc( testAccCheckDHCPOptionsExists(resourceName, &d), acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceVPCDHCPOptions(), resourceName), @@ -167,47 +203,49 @@ func testAccCheckDHCPOptionsExists(n string, v *ec2.DhcpOptions) resource.TestCh } } -func testAccVPCDHCPOptionsConfig_basic(rName string) string { +const testAccVPCDHCPOptionsConfig_basic = ` +resource "aws_vpc_dhcp_options" "test" { + netbios_node_type = 1 +} +` + +func testAccVPCDHCPOptionsConfig_full(rName, domainName string) string { return fmt.Sprintf(` resource "aws_vpc_dhcp_options" "test" { - domain_name = "service.%[1]s" + domain_name = %[2]q domain_name_servers = ["127.0.0.1", "10.0.0.2"] ntp_servers = ["127.0.0.1"] netbios_name_servers = ["127.0.0.1"] - netbios_node_type = 2 + netbios_node_type = "2" + + tags = { + Name = %[1]q + } } -`, rName) +`, rName, domainName) } -func testAccVPCDHCPOptionsConfig_tags1(rName, tagKey1, tagValue1 string) string { +func testAccVPCDHCPOptionsConfig_tags1(tagKey1, tagValue1 string) string { return fmt.Sprintf(` resource "aws_vpc_dhcp_options" "test" { - domain_name = "service.%[1]s" - domain_name_servers = ["127.0.0.1", "10.0.0.2"] - ntp_servers = ["127.0.0.1"] - netbios_name_servers = ["127.0.0.1"] - netbios_node_type = 2 + netbios_node_type = 2 tags = { - %[2]q = %[3]q + %[1]q = %[2]q } } -`, rName, tagKey1, tagValue1) +`, tagKey1, tagValue1) } -func testAccVPCDHCPOptionsConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { +func testAccVPCDHCPOptionsConfig_tags2(tagKey1, tagValue1, tagKey2, tagValue2 string) string { return fmt.Sprintf(` resource "aws_vpc_dhcp_options" "test" { - domain_name = "service.%[1]s" - domain_name_servers = ["127.0.0.1", "10.0.0.2"] - ntp_servers = ["127.0.0.1"] - netbios_name_servers = ["127.0.0.1"] - netbios_node_type = 2 + netbios_node_type = 2 tags = { - %[2]q = %[3]q - %[4]q = %[5]q + %[1]q = %[2]q + %[3]q = %[4]q } } -`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +`, tagKey1, tagValue1, tagKey2, tagValue2) } diff --git a/internal/service/ec2/vpc_subnet.go b/internal/service/ec2/vpc_subnet.go index e52c0c58eba..c3f4ea964f3 100644 --- a/internal/service/ec2/vpc_subnet.go +++ b/internal/service/ec2/vpc_subnet.go @@ -38,7 +38,7 @@ func ResourceSubnet() *schema.Resource { MigrateState: SubnetMigrateState, // Keep in sync with aws_default_subnet's schema. - // See notes in default_subnet.go. + // See notes in vpc_default_subnet.go. Schema: map[string]*schema.Schema{ "arn": { Type: schema.TypeString, diff --git a/website/docs/d/instance.html.markdown b/website/docs/d/instance.html.markdown index 8806d9470cc..d505588c160 100644 --- a/website/docs/d/instance.html.markdown +++ b/website/docs/d/instance.html.markdown @@ -95,6 +95,10 @@ interpolation. * `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_dns_name_options` - The options for the instance hostname. + * `enable_resource_name_dns_aaaa_record` - Indicates whether to respond to DNS queries for instance hostnames with DNS AAAA records. + * `enable_resource_name_dns_a_record` - Indicates whether to respond to DNS queries for instance hostnames with DNS A records. + * `hostname_type` - The type of hostname for EC2 instances. * `private_ip` - The private IP address assigned to the Instance. * `secondary_private_ips` - The secondary private IPv4 addresses assigned to the instance's primary network interface (eth0) in a VPC. * `public_dns` - The public DNS name assigned to the Instance. For EC2-VPC, this diff --git a/website/docs/r/instance.html.markdown b/website/docs/r/instance.html.markdown index af5a2845a15..fe7c44763f9 100644 --- a/website/docs/r/instance.html.markdown +++ b/website/docs/r/instance.html.markdown @@ -124,6 +124,7 @@ The following arguments are supported: * `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. * `placement_partition_number` - (Optional) The number of the partition the instance is in. Valid only if [the `aws_placement_group` resource's](placement_group.html) `strategy` argument is set to `"partition"`. +* `private_dns_name_options` - (Optional) The options for the instance hostname. The default values are inherited from the subnet. See [Private DNS Name Options](#private-dns-name-options) below for more details. * `private_ip` - (Optional) Private IP address to associate with the instance in a VPC. * `root_block_device` - (Optional) Configuration block to customize details about the root block device of the instance. See [Block Devices](#ebs-ephemeral-and-root-block-devices) below for details. When accessing this as an attribute reference, it is a list containing one object. * `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. @@ -266,6 +267,14 @@ Each `network_interface` block supports the following: * `network_card_index` - (Optional) Integer index of the network card. Limited by instance type. The default index is `0`. * `network_interface_id` - (Required) ID of the network interface to attach. +### Private DNS Name Options + +The `private_dns_name_options` block supports the following: + +* `enable_resource_name_dns_aaaa_record` - Indicates whether to respond to DNS queries for instance hostnames with DNS AAAA records. +* `enable_resource_name_dns_a_record` - Indicates whether to respond to DNS queries for instance hostnames with DNS A records. +* `hostname_type` - The type of hostname for Amazon EC2 instances. For IPv4 only subnets, an instance DNS name must be based on the instance IPv4 address. For IPv6 native subnets, an instance DNS name must be based on the instance ID. For dual-stack subnets, you can specify whether DNS names use the instance IPv4 address or the instance ID. Valid values: `ip-name` and `resource-name`. + ### Launch Template Specification -> **Note:** Launch Template parameters will be used only once during instance creation. If you want to update existing instance you need to change parameters