diff --git a/.changelog/36754.txt b/.changelog/36754.txt new file mode 100644 index 00000000000..b446cfcbb1e --- /dev/null +++ b/.changelog/36754.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_ec2_capacity_reservation: Add configurable timeouts +``` + +```release-note:enhancement +resource/aws_ec2_capacity_reservation: Retry `InsufficientInstanceCapacity` errors +``` \ No newline at end of file diff --git a/.changelog/37142.txt b/.changelog/37142.txt new file mode 100644 index 00000000000..fc8b25f4bf8 --- /dev/null +++ b/.changelog/37142.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_launch_template: Add `network_interfaces.primary_ipv6` argument +``` + +```release-note:enhancement +data-source/aws_launch_template: Add `network_interfaces.primary_ipv6` attribute +``` \ No newline at end of file diff --git a/internal/service/ec2/ec2_capacity_reservation.go b/internal/service/ec2/ec2_capacity_reservation.go index ccf820e9cb0..b053796cf74 100644 --- a/internal/service/ec2/ec2_capacity_reservation.go +++ b/internal/service/ec2/ec2_capacity_reservation.go @@ -41,6 +41,12 @@ func resourceCapacityReservation() *schema.Resource { CustomizeDiff: verify.SetTagsDiff, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + Schema: map[string]*schema.Schema{ names.AttrARN: { Type: schema.TypeString, @@ -175,7 +181,7 @@ func resourceCapacityReservationCreate(ctx context.Context, d *schema.ResourceDa d.SetId(aws.ToString(output.CapacityReservation.CapacityReservationId)) - if err := waitCapacityReservationActive(ctx, conn, d.Id()); err != nil { + if _, err := waitCapacityReservationActive(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for EC2 Capacity Reservation (%s) create: %s", d.Id(), err) } @@ -245,7 +251,7 @@ func resourceCapacityReservationUpdate(ctx context.Context, d *schema.ResourceDa return sdkdiag.AppendErrorf(diags, "updating EC2 Capacity Reservation (%s): %s", d.Id(), err) } - if err := waitCapacityReservationActive(ctx, conn, d.Id()); err != nil { + if _, err := waitCapacityReservationActive(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for EC2 Capacity Reservation (%s) update: %s", d.Id(), err) } } @@ -270,7 +276,7 @@ func resourceCapacityReservationDelete(ctx context.Context, d *schema.ResourceDa return sdkdiag.AppendErrorf(diags, "deleting EC2 Capacity Reservation (%s): %s", d.Id(), err) } - if _, err := waitCapacityReservationDeleted(ctx, conn, d.Id()); err != nil { + if _, err := waitCapacityReservationDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for EC2 Capacity Reservation (%s) delete: %s", d.Id(), err) } diff --git a/internal/service/ec2/ec2_launch_template.go b/internal/service/ec2/ec2_launch_template.go index 0c83de0b00a..ca71c429f6a 100644 --- a/internal/service/ec2/ec2_launch_template.go +++ b/internal/service/ec2/ec2_launch_template.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "log" - "strconv" "time" "github.com/aws/aws-sdk-go-v2/aws" @@ -830,6 +829,12 @@ func resourceLaunchTemplate() *schema.Resource { Type: schema.TypeString, Optional: true, }, + "primary_ipv6": { + Type: nullable.TypeNullableBool, + Optional: true, + DiffSuppressFunc: nullable.DiffSuppressNullableBool, + ValidateFunc: nullable.ValidateTypeStringNullableBool, + }, "private_ip_address": { Type: schema.TypeString, Optional: true, @@ -1034,7 +1039,7 @@ func resourceLaunchTemplateRead(ctx context.Context, d *schema.ResourceData, met return sdkdiag.AppendErrorf(diags, "reading EC2 Launch Template (%s): %s", d.Id(), err) } - version := strconv.FormatInt(aws.ToInt64(lt.LatestVersionNumber), 10) + version := flex.Int64ToStringValue(lt.LatestVersionNumber) ltv, err := findLaunchTemplateVersionByTwoPartKey(ctx, conn, d.Id(), version) if err != nil { @@ -1134,9 +1139,9 @@ func resourceLaunchTemplateUpdate(ctx context.Context, d *schema.ResourceData, m } if d.Get("update_default_version").(bool) { - input.DefaultVersion = aws.String(strconv.FormatInt(latestVersion, 10)) + input.DefaultVersion = flex.Int64ValueToString(latestVersion) } else if d.HasChange("default_version") { - input.DefaultVersion = aws.String(strconv.Itoa(d.Get("default_version").(int))) + input.DefaultVersion = flex.IntValueToString(d.Get("default_version").(int)) } _, err := conn.ModifyLaunchTemplate(ctx, input) @@ -2019,6 +2024,10 @@ func expandLaunchTemplateInstanceNetworkInterfaceSpecificationRequest(tfMap map[ apiObject.NetworkInterfaceId = aws.String(v) } + if v, null, _ := nullable.Bool(tfMap["primary_ipv6"].(string)).ValueBool(); !null { + apiObject.PrimaryIpv6 = aws.Bool(v) + } + if v, ok := tfMap[names.AttrSecurityGroups].(*schema.Set); ok && v.Len() > 0 { for _, v := range v.List() { apiObject.Groups = append(apiObject.Groups, v.(string)) @@ -2183,7 +2192,7 @@ func flattenResponseLaunchTemplateData(ctx context.Context, conn *ec2.Client, d d.Set("disable_api_stop", apiObject.DisableApiStop) d.Set("disable_api_termination", apiObject.DisableApiTermination) if apiObject.EbsOptimized != nil { - d.Set("ebs_optimized", strconv.FormatBool(aws.ToBool(apiObject.EbsOptimized))) + d.Set("ebs_optimized", flex.BoolToStringValue(apiObject.EbsOptimized)) } else { d.Set("ebs_optimized", "") } @@ -2341,11 +2350,11 @@ func flattenLaunchTemplateEBSBlockDevice(apiObject *awstypes.LaunchTemplateEbsBl tfMap := map[string]interface{}{} if v := apiObject.DeleteOnTermination; v != nil { - tfMap[names.AttrDeleteOnTermination] = strconv.FormatBool(aws.ToBool(v)) + tfMap[names.AttrDeleteOnTermination] = flex.BoolToStringValue(v) } if v := apiObject.Encrypted; v != nil { - tfMap[names.AttrEncrypted] = strconv.FormatBool(aws.ToBool(v)) + tfMap[names.AttrEncrypted] = flex.BoolToStringValue(v) } if v := apiObject.Iops; v != nil { @@ -2879,15 +2888,15 @@ func flattenLaunchTemplateInstanceNetworkInterfaceSpecification(apiObject awstyp tfMap := map[string]interface{}{} if v := apiObject.AssociateCarrierIpAddress; v != nil { - tfMap["associate_carrier_ip_address"] = strconv.FormatBool(aws.ToBool(v)) + tfMap["associate_carrier_ip_address"] = flex.BoolToStringValue(v) } if v := apiObject.AssociatePublicIpAddress; v != nil { - tfMap["associate_public_ip_address"] = strconv.FormatBool(aws.ToBool(v)) + tfMap["associate_public_ip_address"] = flex.BoolToStringValue(v) } if v := apiObject.DeleteOnTermination; v != nil { - tfMap[names.AttrDeleteOnTermination] = strconv.FormatBool(aws.ToBool(v)) + tfMap[names.AttrDeleteOnTermination] = flex.BoolToStringValue(v) } if v := apiObject.Description; v != nil { @@ -2966,6 +2975,10 @@ func flattenLaunchTemplateInstanceNetworkInterfaceSpecification(apiObject awstyp tfMap[names.AttrNetworkInterfaceID] = aws.ToString(v) } + if v := apiObject.PrimaryIpv6; v != nil { + tfMap["primary_ipv6"] = flex.BoolToStringValue(v) + } + if v := apiObject.PrivateIpAddress; v != nil { tfMap["private_ip_address"] = aws.ToString(v) } diff --git a/internal/service/ec2/ec2_launch_template_data_source.go b/internal/service/ec2/ec2_launch_template_data_source.go index bc2a1912102..ab12b488072 100644 --- a/internal/service/ec2/ec2_launch_template_data_source.go +++ b/internal/service/ec2/ec2_launch_template_data_source.go @@ -6,7 +6,6 @@ package ec2 import ( "context" "fmt" - "strconv" "time" "github.com/aws/aws-sdk-go-v2/aws" @@ -16,6 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/flex" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" @@ -673,6 +673,10 @@ func dataSourceLaunchTemplate() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "primary_ipv6": { + Type: schema.TypeString, + Computed: true, + }, "private_ip_address": { Type: schema.TypeString, Computed: true, @@ -819,7 +823,7 @@ func dataSourceLaunchTemplateRead(ctx context.Context, d *schema.ResourceData, m d.SetId(aws.ToString(lt.LaunchTemplateId)) - version := strconv.FormatInt(aws.ToInt64(lt.LatestVersionNumber), 10) + version := flex.Int64ToStringValue(lt.LatestVersionNumber) ltv, err := findLaunchTemplateVersionByTwoPartKey(ctx, conn, d.Id(), version) if err != nil { diff --git a/internal/service/ec2/ec2_launch_template_test.go b/internal/service/ec2/ec2_launch_template_test.go index 4cce3e6ba7b..d503e0f403c 100644 --- a/internal/service/ec2/ec2_launch_template_test.go +++ b/internal/service/ec2/ec2_launch_template_test.go @@ -875,6 +875,7 @@ func TestAccEC2LaunchTemplate_networkInterface(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "network_interfaces.0.ipv6_prefixes.#", acctest.Ct0), resource.TestCheckResourceAttr(resourceName, "network_interfaces.0.network_card_index", acctest.Ct0), resource.TestCheckResourceAttrSet(resourceName, "network_interfaces.0.network_interface_id"), + resource.TestCheckResourceAttr(resourceName, "network_interfaces.0.primary_ipv6", ""), resource.TestCheckResourceAttr(resourceName, "network_interfaces.0.private_ip_address", ""), resource.TestCheckResourceAttr(resourceName, "network_interfaces.0.security_groups.#", acctest.Ct0), resource.TestCheckResourceAttr(resourceName, "network_interfaces.0.subnet_id", ""), @@ -1391,6 +1392,54 @@ func TestAccEC2LaunchTemplate_instanceMarketOptions(t *testing.T) { }) } +func TestAccEC2LaunchTemplate_primaryIPv6(t *testing.T) { + ctx := acctest.Context(t) + var template awstypes.LaunchTemplate + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_launch_template.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckLaunchTemplateDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccLaunchTemplateConfig_primaryIPv6(rName, acctest.CtTrue), + Check: resource.ComposeTestCheckFunc( + testAccCheckLaunchTemplateExists(ctx, resourceName, &template), + resource.TestCheckResourceAttr(resourceName, "network_interfaces.#", acctest.Ct1), + resource.TestCheckResourceAttrSet(resourceName, "network_interfaces.0.network_interface_id"), + resource.TestCheckResourceAttr(resourceName, "network_interfaces.0.primary_ipv6", acctest.CtTrue), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccLaunchTemplateConfig_primaryIPv6(rName, acctest.CtFalse), + Check: resource.ComposeTestCheckFunc( + testAccCheckLaunchTemplateExists(ctx, resourceName, &template), + resource.TestCheckResourceAttr(resourceName, "network_interfaces.#", acctest.Ct1), + resource.TestCheckResourceAttrSet(resourceName, "network_interfaces.0.network_interface_id"), + resource.TestCheckResourceAttr(resourceName, "network_interfaces.0.primary_ipv6", acctest.CtFalse), + ), + }, + { + Config: testAccLaunchTemplateConfig_primaryIPv6(rName, "null"), + Check: resource.ComposeTestCheckFunc( + testAccCheckLaunchTemplateExists(ctx, resourceName, &template), + resource.TestCheckResourceAttr(resourceName, "network_interfaces.#", acctest.Ct1), + resource.TestCheckResourceAttrSet(resourceName, "network_interfaces.0.network_interface_id"), + resource.TestCheckResourceAttr(resourceName, "network_interfaces.0.primary_ipv6", ""), + ), + }, + }, + }) +} + func TestAccEC2LaunchTemplate_instanceRequirements_memoryMiBAndVCPUCount(t *testing.T) { ctx := acctest.Context(t) var template awstypes.LaunchTemplate @@ -3834,6 +3883,44 @@ resource "aws_launch_template" "test" { `, rName, associatePublicIPAddress) } +func testAccLaunchTemplateConfig_primaryIPv6(rName, primaryIPv6 string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + vpc_id = aws_vpc.test.id + cidr_block = "10.1.0.0/24" + + tags = { + Name = %[1]q + } +} + +resource "aws_network_interface" "test" { + subnet_id = aws_subnet.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_launch_template" "test" { + name = %[1]q + + network_interfaces { + network_interface_id = aws_network_interface.test.id + primary_ipv6 = %[2]s + } +} +`, rName, primaryIPv6) +} + func testAccLaunchTemplateConfig_associateCarrierIPAddress(rName, associateCarrierIPAddress string) string { return fmt.Sprintf(` resource "aws_vpc" "test" { diff --git a/internal/service/ec2/service_package.go b/internal/service/ec2/service_package.go index 268d87e43d6..2fc47c981f2 100644 --- a/internal/service/ec2/service_package.go +++ b/internal/service/ec2/service_package.go @@ -10,10 +10,8 @@ import ( retry_sdkv2 "github.com/aws/aws-sdk-go-v2/aws/retry" ec2_sdkv2 "github.com/aws/aws-sdk-go-v2/service/ec2" aws_sdkv1 "github.com/aws/aws-sdk-go/aws" - request_sdkv1 "github.com/aws/aws-sdk-go/aws/request" session_sdkv1 "github.com/aws/aws-sdk-go/aws/session" ec2_sdkv1 "github.com/aws/aws-sdk-go/service/ec2" - tfawserr_sdkv1 "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" tfawserr_sdkv2 "github.com/hashicorp/aws-sdk-go-base/v2/tfawserr" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -38,21 +36,6 @@ func (p *servicePackage) NewConn(ctx context.Context, config map[string]any) (*e return ec2_sdkv1.New(sess.Copy(&cfg)), nil } -// CustomizeConn customizes a new AWS SDK for Go v1 client for this service package's AWS API. -func (p *servicePackage) CustomizeConn(ctx context.Context, conn *ec2_sdkv1.EC2) (*ec2_sdkv1.EC2, error) { - conn.Handlers.Retry.PushBack(func(r *request_sdkv1.Request) { - switch err := r.Error; r.Operation.Name { - case "RunInstances": - // `InsufficientInstanceCapacity` error has status code 500 and AWS SDK try retry this error by default. - if tfawserr_sdkv1.ErrCodeEquals(err, errCodeInsufficientInstanceCapacity) { - r.Retryable = aws_sdkv1.Bool(false) - } - } - }) - - return conn, nil -} - // NewClient returns a new AWS SDK for Go v2 client for this service package's AWS API. func (p *servicePackage) NewClient(ctx context.Context, config map[string]any) (*ec2_sdkv2.Client, error) { cfg := *(config["aws_sdkv2_config"].(*aws_sdkv2.Config)) @@ -66,6 +49,10 @@ func (p *servicePackage) NewClient(ctx context.Context, config map[string]any) ( return aws_sdkv2.TrueTernary } + if tfawserr_sdkv2.ErrCodeEquals(err, errCodeInsufficientInstanceCapacity) { // CreateCapacityReservation, RunInstances + return aws_sdkv2.TrueTernary + } + if tfawserr_sdkv2.ErrMessageContains(err, errCodeOperationNotPermitted, "Endpoint cannot be created while another endpoint is being created") { // CreateClientVpnEndpoint return aws_sdkv2.TrueTernary } diff --git a/internal/service/ec2/waitv2.go b/internal/service/ec2/waitv2.go index 9f15a048b62..76a39955e07 100644 --- a/internal/service/ec2/waitv2.go +++ b/internal/service/ec2/waitv2.go @@ -57,30 +57,29 @@ func waitAvailabilityZoneGroupNotOptedIn(ctx context.Context, conn *ec2.Client, return nil, err } -const ( - CapacityReservationActiveTimeout = 2 * time.Minute - CapacityReservationDeletedTimeout = 2 * time.Minute -) - -func waitCapacityReservationActive(ctx context.Context, conn *ec2.Client, id string) error { +func waitCapacityReservationActive(ctx context.Context, conn *ec2.Client, id string, timeout time.Duration) (*awstypes.CapacityReservation, error) { //nolint:unparam stateConf := &retry.StateChangeConf{ Pending: enum.Slice(awstypes.CapacityReservationStatePending), Target: enum.Slice(awstypes.CapacityReservationStateActive), Refresh: statusCapacityReservation(ctx, conn, id), - Timeout: CapacityReservationActiveTimeout, + Timeout: timeout, } - _, err := stateConf.WaitForStateContext(ctx) + outputRaw, err := stateConf.WaitForStateContext(ctx) - return err + if output, ok := outputRaw.(*awstypes.CapacityReservation); ok { + return output, err + } + + return nil, err } -func waitCapacityReservationDeleted(ctx context.Context, conn *ec2.Client, id string) (*awstypes.CapacityReservation, error) { +func waitCapacityReservationDeleted(ctx context.Context, conn *ec2.Client, id string, timeout time.Duration) (*awstypes.CapacityReservation, error) { stateConf := &retry.StateChangeConf{ Pending: enum.Slice(awstypes.CapacityReservationStateActive), Target: []string{}, Refresh: statusCapacityReservation(ctx, conn, id), - Timeout: CapacityReservationDeletedTimeout, + Timeout: timeout, } outputRaw, err := stateConf.WaitForStateContext(ctx) diff --git a/website/docs/d/launch_configuration.html.markdown b/website/docs/d/launch_configuration.html.markdown index b88554f70cc..bedeea69b00 100644 --- a/website/docs/d/launch_configuration.html.markdown +++ b/website/docs/d/launch_configuration.html.markdown @@ -41,6 +41,7 @@ This data source exports the following attributes in addition to the arguments a * `http_put_response_hop_limit` - The desired HTTP PUT response hop limit for instance metadata requests. * `security_groups` - List of associated Security Group IDS. * `associate_public_ip_address` - Whether a Public IP address is associated with the instance. +* `primary_ipv6` - Whether the first IPv6 GUA will be made the primary IPv6 address. * `user_data` - User Data of the instance. * `enable_monitoring` - Whether Detailed Monitoring is Enabled. * `ebs_optimized` - Whether the launched EC2 instance will be EBS-optimized. diff --git a/website/docs/r/ec2_capacity_reservation.html.markdown b/website/docs/r/ec2_capacity_reservation.html.markdown index 62f4afcb473..2ef940ffa54 100644 --- a/website/docs/r/ec2_capacity_reservation.html.markdown +++ b/website/docs/r/ec2_capacity_reservation.html.markdown @@ -48,6 +48,14 @@ This resource exports the following attributes in addition to the arguments abov * `arn` - The ARN of the Capacity Reservation. * `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +- `create` - (Default `10m`) +- `update` - (Default `10m`) +- `delete` - (Default `10m`) + ## Import In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Capacity Reservations using the `id`. For example: