Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Private DNS Options to EC2 instance #25161

Merged
merged 23 commits into from
Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7ddbd56
Add Private DNS Options to EC2 instance
der-eismann Jun 3, 2022
10bf17f
Merge branch 'main' into HEAD
ewbankkit Jun 18, 2022
7f5dcfa
r/aws_instance: Make 'private_dns_name_options' Computed and add 'fla…
ewbankkit Jun 18, 2022
02aca58
Add 'expandPrivateDnsNameOptionsRequest'.
ewbankkit Jun 18, 2022
3e76dd5
r/aws_instance: 'private_dns_name_options' is ForceNew.
ewbankkit Jun 18, 2022
ecdb406
'flattenPrivateDnsNameOptionsResponse' -> 'flattenPrivateDNSNameOptio…
ewbankkit Jun 19, 2022
05dfdd6
Check 'private_dns_name_options' in 'TestAccEC2InstanceDataSource_pri…
ewbankkit Jun 19, 2022
dcc82ff
r/aws_instance: Test 'private_dns_name_options'.
ewbankkit Jun 19, 2022
874a717
Add 'FindInstanceTypeOfferings' and friends.
ewbankkit Jun 19, 2022
5359a60
d/aws_ec2_instance_type_offerings: Tidy up.
ewbankkit Jun 19, 2022
d193d41
Remove 'FindInstanceTypeOffering'.
ewbankkit Jun 19, 2022
a414e58
d/aws_ec2_instance_type_offering: Tidy up.
ewbankkit Jun 19, 2022
ad1ba79
Add 'FindInstanceTypes' and friends.
ewbankkit Jun 19, 2022
746b294
d/aws_ec2_instance_types: Tidy up.
ewbankkit Jun 19, 2022
ed0237e
d/aws_ec2_instance_type: Tidy up.
ewbankkit Jun 19, 2022
a22f076
Add 'ConfigLatestAmazonLinux2HVMEBSARM64AMI' and friends.
ewbankkit Jun 19, 2022
1ca7a99
r/aws_instance: Add 'TestAccEC2Instance_CreditSpecificationUnknownCPU…
ewbankkit Jun 19, 2022
a4f5440
r/aws_instance: Use 'CPUCredits_Values()'.
ewbankkit Jun 19, 2022
4c9f12e
r/aws_default_vpc_dhcp_options: Tidy up.
ewbankkit Jun 20, 2022
a1f6e93
r/aws_vpc_dhcp_options: At least one option must be configured.
ewbankkit Jun 20, 2022
5fb1dfb
Add and test 'ParseInstanceType'.
ewbankkit Jun 20, 2022
dff66a0
aws_instance/aws_launch_template: Correctly handle 'credit_specificat…
ewbankkit Jun 20, 2022
2bf25b1
r/aws_launch_template: Check non-empty instance type.
ewbankkit Jun 20, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .changelog/25161.txt
Original file line number Diff line number Diff line change
@@ -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
```
38 changes: 38 additions & 0 deletions internal/acctest/acctest.go
Original file line number Diff line number Diff line change
Expand Up @@ -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" {}
Expand Down
17 changes: 5 additions & 12 deletions internal/service/autoscaling/group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down
182 changes: 161 additions & 21 deletions internal/service/ec2/ec2_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"log"
"regexp"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -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() == "" {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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 {
Expand Down Expand Up @@ -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...")
}
}

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Loading