Skip to content

Commit

Permalink
Merge pull request #25161 from der-eismann/add-ec2-instance-private-d…
Browse files Browse the repository at this point in the history
…ns-options

Add Private DNS Options to EC2 instance
  • Loading branch information
ewbankkit authored Jun 20, 2022
2 parents 810f853 + 2bf25b1 commit 748efbe
Show file tree
Hide file tree
Showing 26 changed files with 1,056 additions and 531 deletions.
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

0 comments on commit 748efbe

Please sign in to comment.