diff --git a/.changelog/45142.txt b/.changelog/45142.txt new file mode 100644 index 000000000000..ee83613b14c4 --- /dev/null +++ b/.changelog/45142.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resoource/aws_ecs_capacity_provider: Add `managed_instances_provider.infrastructure_optimization` argument +``` \ No newline at end of file diff --git a/internal/service/ecs/capacity_provider.go b/internal/service/ecs/capacity_provider.go index 8545dbaa7da0..35bcba6f9c8e 100644 --- a/internal/service/ecs/capacity_provider.go +++ b/internal/service/ecs/capacity_provider.go @@ -74,12 +74,6 @@ func resourceCapacityProvider() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "cluster": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validateClusterName, - }, "auto_scaling_group_provider": { Type: schema.TypeList, MaxItems: 1, @@ -147,6 +141,12 @@ func resourceCapacityProvider() *schema.Resource { }, }, }, + "cluster": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validateClusterName, + }, "managed_instances_provider": { Type: schema.TypeList, MaxItems: 1, @@ -154,6 +154,20 @@ func resourceCapacityProvider() *schema.Resource { ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "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), + }, + }, + }, + }, "infrastructure_role_arn": { Type: schema.TypeString, Required: true, @@ -885,9 +899,9 @@ func flattenAutoScalingGroupProvider(provider *awstypes.AutoScalingGroupProvider p := map[string]any{ "auto_scaling_group_arn": aws.ToString(provider.AutoScalingGroupArn), - "managed_draining": string(provider.ManagedDraining), + "managed_draining": provider.ManagedDraining, "managed_scaling": []map[string]any{}, - "managed_termination_protection": string(provider.ManagedTerminationProtection), + "managed_termination_protection": provider.ManagedTerminationProtection, } if provider.ManagedScaling != nil { @@ -895,7 +909,7 @@ func flattenAutoScalingGroupProvider(provider *awstypes.AutoScalingGroupProvider "instance_warmup_period": aws.ToInt32(provider.ManagedScaling.InstanceWarmupPeriod), "maximum_scaling_step_size": aws.ToInt32(provider.ManagedScaling.MaximumScalingStepSize), "minimum_scaling_step_size": aws.ToInt32(provider.ManagedScaling.MinimumScalingStepSize), - names.AttrStatus: string(provider.ManagedScaling.Status), + names.AttrStatus: provider.ManagedScaling.Status, "target_capacity": aws.ToInt32(provider.ManagedScaling.TargetCapacity), } @@ -918,6 +932,10 @@ func expandManagedInstancesProviderCreate(configured any) *awstypes.CreateManage tfMap := configured.([]any)[0].(map[string]any) apiObject := &awstypes.CreateManagedInstancesProviderConfiguration{} + if v, ok := tfMap["infrastructure_optimization"].([]any); ok && len(v) > 0 { + apiObject.InfrastructureOptimization = expandInfrastructureOptimization(v) + } + if v, ok := tfMap["infrastructure_role_arn"].(string); ok && v != "" { apiObject.InfrastructureRoleArn = aws.String(v) } @@ -945,6 +963,10 @@ func expandManagedInstancesProviderUpdate(configured any) *awstypes.UpdateManage tfMap := configured.([]any)[0].(map[string]any) apiObject := &awstypes.UpdateManagedInstancesProviderConfiguration{} + if v, ok := tfMap["infrastructure_optimization"].([]any); ok && len(v) > 0 { + apiObject.InfrastructureOptimization = expandInfrastructureOptimization(v) + } + if v, ok := tfMap["infrastructure_role_arn"].(string); ok && v != "" { apiObject.InfrastructureRoleArn = aws.String(v) } @@ -960,6 +982,21 @@ func expandManagedInstancesProviderUpdate(configured any) *awstypes.UpdateManage 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 +} + func expandInstanceLaunchTemplateCreate(tfList []any) *awstypes.InstanceLaunchTemplate { if len(tfList) == 0 || tfList[0] == nil { return nil @@ -1341,13 +1378,29 @@ func flattenManagedInstancesProvider(provider *awstypes.ManagedInstancesProvider tfMap := map[string]any{ "infrastructure_role_arn": aws.ToString(provider.InfrastructureRoleArn), - names.AttrPropagateTags: string(provider.PropagateTags), + names.AttrPropagateTags: provider.PropagateTags, } if provider.InstanceLaunchTemplate != nil { 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} } @@ -1358,7 +1411,7 @@ func flattenInstanceLaunchTemplate(template *awstypes.InstanceLaunchTemplate) [] tfMap := map[string]any{ "ec2_instance_profile_arn": aws.ToString(template.Ec2InstanceProfileArn), - "monitoring": string(template.Monitoring), + "monitoring": template.Monitoring, } if template.InstanceRequirements != nil { @@ -1390,9 +1443,9 @@ func flattenInstanceRequirementsRequest(req *awstypes.InstanceRequirementsReques } tfMap := map[string]any{ - "bare_metal": string(req.BareMetal), - "burstable_performance": string(req.BurstablePerformance), - "local_storage": string(req.LocalStorage), + "bare_metal": req.BareMetal, + "burstable_performance": req.BurstablePerformance, + "local_storage": req.LocalStorage, "max_spot_price_as_percentage_of_optimal_on_demand_price": aws.ToInt32(req.MaxSpotPriceAsPercentageOfOptimalOnDemandPrice), "on_demand_max_price_percentage_over_lowest_price": aws.ToInt32(req.OnDemandMaxPricePercentageOverLowestPrice), "require_hibernate_support": aws.ToBool(req.RequireHibernateSupport), diff --git a/internal/service/ecs/capacity_provider_test.go b/internal/service/ecs/capacity_provider_test.go index 254f5919f907..2cd78ddf8813 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 13656f8df8a8..98344c49dabb 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: