From 7a53e1673f4b186c2fe9334e6acba4e7087d4a4f Mon Sep 17 00:00:00 2001 From: Kishan Gajjar Date: Mon, 17 Nov 2025 16:18:30 -0500 Subject: [PATCH 1/4] feat(ecs): Mananged instances provider with infrastructure optimization --- internal/service/ecs/capacity_provider.go | 53 ++++++++ .../service/ecs/capacity_provider_test.go | 113 +++++++++++++++++- .../r/ecs_capacity_provider.html.markdown | 8 ++ 3 files changed, 169 insertions(+), 5 deletions(-) diff --git a/internal/service/ecs/capacity_provider.go b/internal/service/ecs/capacity_provider.go index 8545dbaa7da0..0c265b85d322 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 254f5919f907..bdd7ede27b46 100644 --- a/internal/service/ecs/capacity_provider_test.go +++ b/internal/service/ecs/capacity_provider_test.go @@ -416,6 +416,52 @@ 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"), + ), + }, + // { + // ResourceName: resourceName, + // ImportState: true, + // ImportStateVerify: true, + // }, + { + 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) @@ -434,7 +480,7 @@ func testAccCheckCapacityProviderDestroy(ctx context.Context) resource.TestCheck return err } - return fmt.Errorf("ECS Capacity Provider ID %s still exists", rs.Primary.ID) + return fmt.Errorf("ECS Capacity ProviderID %s still exists", rs.Primary.ID) } return nil @@ -705,6 +751,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 +833,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 +856,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 +895,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 +932,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 +950,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 13656f8df8a8..af5f687f17ff 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 infrastastructure 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: From 1579df7d6fe3aa6e27686dd84de4049880644473 Mon Sep 17 00:00:00 2001 From: Kishan Gajjar Date: Mon, 17 Nov 2025 16:35:14 -0500 Subject: [PATCH 2/4] correct doc and remove comment lines --- internal/service/ecs/capacity_provider_test.go | 7 +------ website/docs/r/ecs_capacity_provider.html.markdown | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/internal/service/ecs/capacity_provider_test.go b/internal/service/ecs/capacity_provider_test.go index bdd7ede27b46..2dbae4bb43f3 100644 --- a/internal/service/ecs/capacity_provider_test.go +++ b/internal/service/ecs/capacity_provider_test.go @@ -437,11 +437,6 @@ func TestAccECSCapacityProvider_createManagedInstancesProvider_withInfrastructur resource.TestCheckResourceAttr(resourceName, "managed_instances_provider.0.infrastructure_optimization.0.scale_in_after", "300"), ), }, - // { - // ResourceName: resourceName, - // ImportState: true, - // ImportStateVerify: true, - // }, { Config: testAccCapacityProviderConfig_managedInstancesProvider_withInfrastructureOptimization(rName, 0), Check: resource.ComposeAggregateTestCheckFunc( @@ -480,7 +475,7 @@ func testAccCheckCapacityProviderDestroy(ctx context.Context) resource.TestCheck return err } - return fmt.Errorf("ECS Capacity ProviderID %s still exists", rs.Primary.ID) + return fmt.Errorf("ECS Capacity Provider ID %s still exists", rs.Primary.ID) } return nil diff --git a/website/docs/r/ecs_capacity_provider.html.markdown b/website/docs/r/ecs_capacity_provider.html.markdown index af5f687f17ff..6e2aad6f918f 100644 --- a/website/docs/r/ecs_capacity_provider.html.markdown +++ b/website/docs/r/ecs_capacity_provider.html.markdown @@ -122,7 +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 infrastastructure 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. +* `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` From 79d052e8fd00bbb59113000ce5c9592df1016629 Mon Sep 17 00:00:00 2001 From: Kishan Gajjar Date: Thu, 20 Nov 2025 09:52:22 -0500 Subject: [PATCH 3/4] fix build failures --- internal/service/ecs/capacity_provider_test.go | 2 +- website/docs/r/ecs_capacity_provider.html.markdown | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/service/ecs/capacity_provider_test.go b/internal/service/ecs/capacity_provider_test.go index 2dbae4bb43f3..029a77e5ac4f 100644 --- a/internal/service/ecs/capacity_provider_test.go +++ b/internal/service/ecs/capacity_provider_test.go @@ -954,7 +954,7 @@ resource "aws_ecs_capacity_provider" "test" { managed_instances_provider { infrastructure_role_arn = aws_iam_role.test.arn - propagate_tags = "NONE" + propagate_tags = "NONE" instance_launch_template { ec2_instance_profile_arn = aws_iam_instance_profile.test.arn diff --git a/website/docs/r/ecs_capacity_provider.html.markdown b/website/docs/r/ecs_capacity_provider.html.markdown index 6e2aad6f918f..98344c49dabb 100644 --- a/website/docs/r/ecs_capacity_provider.html.markdown +++ b/website/docs/r/ecs_capacity_provider.html.markdown @@ -171,9 +171,9 @@ This resource supports the following arguments: ### `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. + * 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 From 058651190e684a7ae43b5a18fecab10cc3ba212e Mon Sep 17 00:00:00 2001 From: Kishan Gajjar Date: Thu, 20 Nov 2025 10:22:23 -0500 Subject: [PATCH 4/4] fix build failures --- internal/service/ecs/capacity_provider_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/ecs/capacity_provider_test.go b/internal/service/ecs/capacity_provider_test.go index 029a77e5ac4f..2cd78ddf8813 100644 --- a/internal/service/ecs/capacity_provider_test.go +++ b/internal/service/ecs/capacity_provider_test.go @@ -954,7 +954,7 @@ resource "aws_ecs_capacity_provider" "test" { managed_instances_provider { infrastructure_role_arn = aws_iam_role.test.arn - propagate_tags = "NONE" + propagate_tags = "NONE" instance_launch_template { ec2_instance_profile_arn = aws_iam_instance_profile.test.arn