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 support for enabling primary ipv6 address on EC2 instance #36425

Merged
merged 23 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
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
7 changes: 7 additions & 0 deletions .changelog/36425.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:enhancement
resource/aws_instance: Add `enable_primary_ipv6` argument to add support for enabling primary IPv6 addresses on EC2 instances
```

```release-note:enhancement
resource/aws_network_interface: Add `enable_primary_ipv6` argument to add support for enabling primary IPv6 addresses for network interfaces
```
58 changes: 57 additions & 1 deletion internal/service/ec2/ec2_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,15 @@
},
},
},
"enable_primary_ipv6": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
AtLeastOneOf: []string{
"ipv6_address_count",
"ipv6_addresses",
},
},
"ephemeral_block_device": {
Type: schema.TypeSet,
Optional: true,
Expand Down Expand Up @@ -588,7 +597,7 @@
Computed: true,
},
"network_interface": {
ConflictsWith: []string{"associate_public_ip_address", names.AttrSubnetID, "private_ip", "secondary_private_ips", names.AttrVPCSecurityGroupIDs, names.AttrSecurityGroups, "ipv6_addresses", "ipv6_address_count", "source_dest_check"},
ConflictsWith: []string{"associate_public_ip_address", "enable_primary_ipv6", names.AttrSubnetID, "private_ip", "secondary_private_ips", names.AttrVPCSecurityGroupIDs, names.AttrSecurityGroups, "ipv6_addresses", "ipv6_address_count", "source_dest_check"},
Type: schema.TypeSet,
Optional: true,
Computed: true,
Expand Down Expand Up @@ -907,6 +916,10 @@
customdiff.ForceNewIf("user_data_base64", func(_ context.Context, diff *schema.ResourceDiff, meta interface{}) bool {
return diff.Get("user_data_replace_on_change").(bool)
}),
customdiff.ForceNewIf("enable_primary_ipv6", func(_ context.Context, diff *schema.ResourceDiff, meta interface{}) bool {
o, n := diff.GetChange("enable_primary_ipv6")
return o.(bool) && !n.(bool) // can be enabled but not disabled without recreate
}),
customdiff.ForceNewIf(names.AttrInstanceType, func(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) bool {
conn := meta.(*conns.AWSClient).EC2Client(ctx)

Expand Down Expand Up @@ -989,6 +1002,7 @@
DisableApiTermination: instanceOpts.DisableAPITermination,
EbsOptimized: instanceOpts.EBSOptimized,
EnclaveOptions: instanceOpts.EnclaveOptions,
EnablePrimaryIpv6: instanceOpts.EnablePrimaryIpv6,
HibernationOptions: instanceOpts.HibernationOptions,
IamInstanceProfile: instanceOpts.IAMInstanceProfile,
ImageId: instanceOpts.ImageID,
Expand Down Expand Up @@ -1298,6 +1312,12 @@
for _, address := range primaryNetworkInterface.Ipv6Addresses {
ipv6Addresses = append(ipv6Addresses, aws.ToString(address.Ipv6Address))
}

if len(primaryNetworkInterface.Ipv6Addresses) > 0 {
if err := d.Set("enable_primary_ipv6", primaryNetworkInterface.Ipv6Addresses[0].IsPrimaryIpv6); err != nil {
return sdkdiag.AppendErrorf(diags, "setting enable_primary_ipv6: %s", err)
}
}
}
} else {
d.Set("associate_public_ip_address", instance.PublicIpAddress != nil)
Expand Down Expand Up @@ -1630,6 +1650,37 @@
}
}

if d.HasChange("enable_primary_ipv6") && !d.IsNewResource() {
instance, err := FindInstanceByID(ctx, conn, d.Id())
if err != nil {
return sdkdiag.AppendErrorf(diags, "reading EC2 Instance (%s): %s", d.Id(), err)
}

var primaryInterface *awstypes.InstanceNetworkInterface
for _, ni := range instance.NetworkInterfaces {
if aws.ToInt32(ni.Attachment.DeviceIndex) == 0 {
primaryInterface = &ni
}
}

if primaryInterface == nil {
return sdkdiag.AppendErrorf(diags, "modifying EC2 Instance (%s), enable_primary_ipv6, which does not contain a primary network interface", d.Id())
}

enablePrimaryIpv6 := d.Get("enable_primary_ipv6").(bool)

input := ec2.ModifyNetworkInterfaceAttributeInput{
NetworkInterfaceId: primaryInterface.NetworkInterfaceId,
EnablePrimaryIpv6: aws.Bool(enablePrimaryIpv6),
}

_, err = conn.ModifyNetworkInterfaceAttribute(ctx, &input)
if err != nil {
return sdkdiag.AppendErrorf(diags, "modifying EC2 Instance (%s) primary network interface: %s", d.Id(), err)
}

}

Check failure on line 1682 in internal/service/ec2/ec2_instance.go

View workflow job for this annotation

GitHub Actions / 3 of 3

unnecessary trailing newline (whitespace)

if d.HasChange("ipv6_address_count") && !d.IsNewResource() {
instance, err := findInstanceByID(ctx, conn, d.Id())
if err != nil {
Expand Down Expand Up @@ -2875,6 +2926,7 @@
DisableAPIStop *bool
DisableAPITermination *bool
EBSOptimized *bool
EnablePrimaryIpv6 *bool
EnclaveOptions *awstypes.EnclaveOptionsRequest
HibernationOptions *awstypes.HibernationOptionsRequest
IAMInstanceProfile *awstypes.IamInstanceProfileSpecification
Expand Down Expand Up @@ -3085,6 +3137,10 @@
opts.SecurityGroups = groups
}

if v, ok := d.GetOk("enable_primary_ipv6"); ok {
opts.EnablePrimaryIpv6 = aws.Bool(v.(bool))
}

if v, ok := d.GetOk("ipv6_address_count"); ok {
opts.Ipv6AddressCount = aws.Int32(int32(v.(int)))
}
Expand Down
93 changes: 93 additions & 0 deletions internal/service/ec2/ec2_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,80 @@ func TestAccEC2Instance_ipv6AddressCountAndSingleAddressCausesError(t *testing.T
})
}

func TestAccEC2Instance_IPv6_primaryEnable(t *testing.T) {
ctx := acctest.Context(t)
var original, updated awstypes.Instance
resourceName := "aws_instance.test"
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckInstanceDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccInstanceConfig_enablePrimaryIPv6(rName, false),
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists(ctx, resourceName, &original),
resource.TestCheckResourceAttr(resourceName, "ipv6_address_count", "1"),
),
},
{
Config: testAccInstanceConfig_enablePrimaryIPv6(rName, true),
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists(ctx, resourceName, &updated),
testAccCheckInstanceNotRecreated(&original, &updated),
resource.TestCheckResourceAttr(resourceName, "ipv6_address_count", "1"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"user_data_replace_on_change"},
},
},
})
}

func TestAccEC2Instance_IPv6_primaryDisable(t *testing.T) {
ctx := acctest.Context(t)
var original, updated awstypes.Instance
resourceName := "aws_instance.test"
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckInstanceDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccInstanceConfig_enablePrimaryIPv6(rName, true),
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists(ctx, resourceName, &original),
resource.TestCheckResourceAttr(resourceName, "ipv6_address_count", "1"),
),
},
{
Config: testAccInstanceConfig_enablePrimaryIPv6(rName, false),
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists(ctx, resourceName, &updated),
testAccCheckInstanceRecreated(&original, &updated),
resource.TestCheckResourceAttr(resourceName, "ipv6_address_count", "1"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"user_data_replace_on_change"},
},
},
})
}

func TestAccEC2Instance_IPv6_supportAddressCountWithIPv4(t *testing.T) {
ctx := acctest.Context(t)
var v awstypes.Instance
Expand Down Expand Up @@ -6789,6 +6863,25 @@ resource "aws_instance" "test" {
`, rName))
}

func testAccInstanceConfig_enablePrimaryIPv6(rName string, primaryIPv6 bool) string {
return acctest.ConfigCompose(
acctest.ConfigLatestAmazonLinux2HVMEBSX8664AMI(),
testAccInstanceVPCIPv6Config(rName),
fmt.Sprintf(`
resource "aws_instance" "test" {
ami = data.aws_ami.amzn2-ami-minimal-hvm-ebs-x86_64.id
instance_type = "t2.micro"
subnet_id = aws_subnet.test.id
enable_primary_ipv6 = %[1]t
ipv6_address_count = 1

tags = {
Name = %[2]q
}
}
`, primaryIPv6, rName))
}

func testAccInstanceConfig_ipv6Support(rName string) string {
return acctest.ConfigCompose(
acctest.ConfigLatestAmazonLinux2HVMEBSX8664AMI(),
Expand Down
35 changes: 35 additions & 0 deletions internal/service/ec2/vpc_network_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ func resourceNetworkInterface() *schema.Resource {
Type: schema.TypeString,
Optional: true,
},
"enable_primary_ipv6": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
AtLeastOneOf: []string{
"ipv6_address_count",
"ipv6_addresses",
"ipv6_address_list",
},
},
"interface_type": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -237,6 +247,10 @@ func resourceNetworkInterface() *schema.Resource {
return false
}
}),
customdiff.ForceNewIf("enable_primary_ipv6", func(_ context.Context, diff *schema.ResourceDiff, meta interface{}) bool {
o, n := diff.GetChange("enable_primary_ipv6")
return o.(bool) && !n.(bool) // can be enabled but not disabled without recreate
}),
customdiff.ForceNewIf("private_ip_list", func(_ context.Context, d *schema.ResourceDiff, meta interface{}) bool {
privateIPListEnabled := d.Get("private_ip_list_enabled").(bool)
if !privateIPListEnabled {
Expand Down Expand Up @@ -351,6 +365,10 @@ func resourceNetworkInterfaceCreate(ctx context.Context, d *schema.ResourceData,
input.Description = aws.String(v.(string))
}

if v, ok := d.GetOk("enable_primary_ipv6"); ok {
input.EnablePrimaryIpv6 = aws.Bool(v.(bool))
}

if v, ok := d.GetOk("interface_type"); ok {
input.InterfaceType = types.NetworkInterfaceCreationType(v.(string))
}
Expand Down Expand Up @@ -532,6 +550,11 @@ func resourceNetworkInterfaceRead(ctx context.Context, d *schema.ResourceData, m
}
d.Set("ipv4_prefix_count", len(eni.Ipv4Prefixes))
d.Set("ipv6_address_count", len(eni.Ipv6Addresses))
if len(eni.Ipv6Addresses) > 0 {
if err := d.Set("enable_primary_ipv6", eni.Ipv6Addresses[0].IsPrimaryIpv6); err != nil {
return sdkdiag.AppendErrorf(diags, "setting enable_primary_ipv6: %s", err)
}
}
if err := d.Set("ipv6_address_list", flattenNetworkInterfaceIPv6Addresses(eni.Ipv6Addresses)); err != nil {
return sdkdiag.AppendErrorf(diags, "setting ipv6 address list: %s", err)
}
Expand Down Expand Up @@ -803,6 +826,18 @@ func resourceNetworkInterfaceUpdate(ctx context.Context, d *schema.ResourceData,
}
}

if d.HasChange("enable_primary_ipv6") {
input := &ec2.ModifyNetworkInterfaceAttributeInput{
NetworkInterfaceId: aws.String(d.Id()),
EnablePrimaryIpv6: aws.Bool(d.Get("enable_primary_ipv6").(bool)),
}

_, err := conn.ModifyNetworkInterfaceAttribute(ctx, input)
if err != nil {
return sdkdiag.AppendErrorf(diags, "modifying EC2 Network Interface (%s) enable primary IPv6: %s", d.Id(), err)
}
}

if d.HasChange("ipv6_addresses") && !d.Get("ipv6_address_list_enabled").(bool) {
o, n := d.GetChange("ipv6_addresses")
if o == nil {
Expand Down
Loading
Loading