diff --git a/internal/service/ecs/capacity_provider.go b/internal/service/ecs/capacity_provider.go index 8545dbaa7da..0c265b85d32 100644 --- a/internal/service/ecs/capacity_provider.go +++ b/internal/service/ecs/capacity_provider.go @@ -506,6 +506,20 @@ func resourceCapacityProvider() *schema.Resource { Optional: true, ValidateDiagFunc: enum.Validate[awstypes.PropagateMITags](), }, + "infrastructure_optimization": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "scale_in_after": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(-1, 3600), + }, + }, + }, + }, }, }, }, @@ -930,6 +944,10 @@ func expandManagedInstancesProviderCreate(configured any) *awstypes.CreateManage apiObject.PropagateTags = awstypes.PropagateMITags(v) } + if v, ok := tfMap["infrastructure_optimization"].([]any); ok && len(v) > 0 { + apiObject.InfrastructureOptimization = expandInfrastructureOptimization(v) + } + return apiObject } @@ -957,6 +975,25 @@ func expandManagedInstancesProviderUpdate(configured any) *awstypes.UpdateManage apiObject.PropagateTags = awstypes.PropagateMITags(v) } + if v, ok := tfMap["infrastructure_optimization"].([]any); ok && len(v) > 0 { + apiObject.InfrastructureOptimization = expandInfrastructureOptimization(v) + } + + return apiObject +} + +func expandInfrastructureOptimization(tfList []any) *awstypes.InfrastructureOptimization { + if len(tfList) == 0 || tfList[0] == nil { + return nil + } + + tfMap := tfList[0].(map[string]any) + apiObject := &awstypes.InfrastructureOptimization{} + + if v, ok := tfMap["scale_in_after"].(int); ok { + apiObject.ScaleInAfter = aws.Int32(int32(v)) + } + return apiObject } @@ -1348,6 +1385,22 @@ func flattenManagedInstancesProvider(provider *awstypes.ManagedInstancesProvider tfMap["instance_launch_template"] = flattenInstanceLaunchTemplate(provider.InstanceLaunchTemplate) } + if provider.InfrastructureOptimization != nil { + tfMap["infrastructure_optimization"] = flattenInfrastructureOptimization(provider.InfrastructureOptimization) + } + + return []map[string]any{tfMap} +} + +func flattenInfrastructureOptimization(apiObject *awstypes.InfrastructureOptimization) []map[string]any { + if apiObject == nil { + return nil + } + + tfMap := map[string]any{ + "scale_in_after": aws.ToInt32(apiObject.ScaleInAfter), + } + return []map[string]any{tfMap} } diff --git a/internal/service/ecs/capacity_provider_test.go b/internal/service/ecs/capacity_provider_test.go index 254f5919f90..2cd78ddf881 100644 --- a/internal/service/ecs/capacity_provider_test.go +++ b/internal/service/ecs/capacity_provider_test.go @@ -416,6 +416,47 @@ func TestAccECSCapacityProvider_updateManagedInstancesProvider(t *testing.T) { }) } +func TestAccECSCapacityProvider_createManagedInstancesProvider_withInfrastructureOptimization(t *testing.T) { + ctx := acctest.Context(t) + var provider awstypes.CapacityProvider + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ecs_capacity_provider.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.ECSServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCapacityProviderDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCapacityProviderConfig_managedInstancesProvider_withInfrastructureOptimization(rName, 300), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckCapacityProviderExists(ctx, resourceName, &provider), + resource.TestCheckResourceAttr(resourceName, "managed_instances_provider.#", "1"), + resource.TestCheckResourceAttr(resourceName, "managed_instances_provider.0.infrastructure_optimization.#", "1"), + resource.TestCheckResourceAttr(resourceName, "managed_instances_provider.0.infrastructure_optimization.0.scale_in_after", "300"), + ), + }, + { + Config: testAccCapacityProviderConfig_managedInstancesProvider_withInfrastructureOptimization(rName, 0), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckCapacityProviderExists(ctx, resourceName, &provider), + resource.TestCheckResourceAttr(resourceName, "managed_instances_provider.0.infrastructure_optimization.#", "1"), + resource.TestCheckResourceAttr(resourceName, "managed_instances_provider.0.infrastructure_optimization.0.scale_in_after", "0"), + ), + }, + { + Config: testAccCapacityProviderConfig_managedInstancesProvider_withInfrastructureOptimization(rName, -1), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckCapacityProviderExists(ctx, resourceName, &provider), + resource.TestCheckResourceAttr(resourceName, "managed_instances_provider.0.infrastructure_optimization.#", "1"), + resource.TestCheckResourceAttr(resourceName, "managed_instances_provider.0.infrastructure_optimization.0.scale_in_after", "-1"), + ), + }, + }, + }) +} + func testAccCheckCapacityProviderDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).ECSClient(ctx) @@ -705,6 +746,22 @@ resource "aws_subnet" "test" { } } +resource "aws_security_group" "test" { + name = %[1]q + vpc_id = aws_vpc.test.id + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = %[1]q + } +} + resource "aws_ecs_cluster" "test" { name = %[1]q } @@ -771,7 +828,8 @@ resource "aws_ecs_capacity_provider" "test" { ec2_instance_profile_arn = aws_iam_instance_profile.test.arn network_configuration { - subnets = aws_subnet.test[*].id + subnets = aws_subnet.test[*].id + security_groups = [aws_security_group.test.id] } } } @@ -793,7 +851,8 @@ resource "aws_ecs_capacity_provider" "test" { ec2_instance_profile_arn = aws_iam_instance_profile.test.arn network_configuration { - subnets = aws_subnet.test[*].id + subnets = aws_subnet.test[*].id + security_groups = [aws_security_group.test.id] } instance_requirements { @@ -831,7 +890,8 @@ resource "aws_ecs_capacity_provider" "test" { ec2_instance_profile_arn = aws_iam_instance_profile.test.arn network_configuration { - subnets = aws_subnet.test[*].id + subnets = aws_subnet.test[*].id + security_groups = [aws_security_group.test.id] } storage_configuration { @@ -867,7 +927,8 @@ resource "aws_ecs_capacity_provider" "test" { ec2_instance_profile_arn = aws_iam_instance_profile.test.arn network_configuration { - subnets = aws_subnet.test[*].id + subnets = aws_subnet.test[*].id + security_groups = [aws_security_group.test.id] } instance_requirements { @@ -884,3 +945,40 @@ resource "aws_ecs_capacity_provider" "test" { } `, rName)) } + +func testAccCapacityProviderConfig_managedInstancesProvider_withInfrastructureOptimization(rName string, scaleInAfter int) string { + return acctest.ConfigCompose(testAccCapacityProviderConfig_managedInstancesProvider_base(rName), fmt.Sprintf(` +resource "aws_ecs_capacity_provider" "test" { + name = %[1]q + cluster = aws_ecs_cluster.test.name + + managed_instances_provider { + infrastructure_role_arn = aws_iam_role.test.arn + propagate_tags = "NONE" + + instance_launch_template { + ec2_instance_profile_arn = aws_iam_instance_profile.test.arn + + network_configuration { + subnets = aws_subnet.test[*].id + security_groups = [aws_security_group.test.id] + } + + instance_requirements { + vcpu_count { + min = 1 + } + + memory_mib { + min = 1024 + } + } + } + + infrastructure_optimization { + scale_in_after = %[2]d + } + } +} +`, rName, scaleInAfter)) +} diff --git a/website/docs/r/ecs_capacity_provider.html.markdown b/website/docs/r/ecs_capacity_provider.html.markdown index 13656f8df8a..98344c49dab 100644 --- a/website/docs/r/ecs_capacity_provider.html.markdown +++ b/website/docs/r/ecs_capacity_provider.html.markdown @@ -122,6 +122,7 @@ This resource supports the following arguments: * `infrastructure_role_arn` - (Required) The Amazon Resource Name (ARN) of the infrastructure role that Amazon ECS uses to manage instances on your behalf. This role must have permissions to launch, terminate, and manage Amazon EC2 instances, as well as access to other AWS services required for Amazon ECS Managed Instances functionality. For more information, see [Amazon ECS infrastructure IAM role](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/infrastructure_IAM_role.html) in the Amazon ECS Developer Guide. * `instance_launch_template` - (Required) The launch template configuration that specifies how Amazon ECS should launch Amazon EC2 instances. This includes the instance profile, network configuration, storage settings, and instance requirements for attribute-based instance type selection. For more information, see [Store instance launch parameters in Amazon EC2 launch templates](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-launch-templates.html) in the Amazon EC2 User Guide. Detailed below. * `propagate_tags` - (Optional) Specifies whether to propagate tags from the capacity provider to the Amazon ECS Managed Instances. When enabled, tags applied to the capacity provider are automatically applied to all instances launched by this provider. Valid values are `CAPACITY_PROVIDER` and `NONE`. +* `infrastructure_optimization` - (Optional) Defines how Amazon ECS Managed Instances optimizes the infrastructure in your capacity provider. Configure it to turn on or off the infrastructure optimization in your capacity provider, and to control the idle EC2 instances optimization delay. ### `instance_launch_template` @@ -167,6 +168,13 @@ This resource supports the following arguments: * `total_local_storage_gb` - (Optional) The minimum and maximum total local storage in gigabytes (GB) for instance types with local storage. * `vcpu_count` - (Required) The minimum and maximum number of vCPUs for the instance types. Amazon ECS selects instance types that have vCPU counts within this range. +### `infrastructure_optimization` + +* `scale_in_after` - (Optional) This parameter defines the number of seconds Amazon ECS Managed Instances waits before optimizing EC2 instances that have become idle or underutilized. A longer delay increases the likelihood of placing new tasks on idle instances, reducing startup time. A shorter delay helps reduce infrastructure costs by optimizing idle instances more quickly. Valid values are: + * Not set (null) - Uses the default optimization behavior. + * `-1` - Disables automatic infrastructure optimization. + * `0` to `3600` (inclusive) - Specifies the number of seconds to wait before optimizing instances. + ## Attribute Reference This resource exports the following attributes in addition to the arguments above: