Skip to content

Commit

Permalink
Merge pull request #28384 from mihneaspirescu/f-28259-eventbridge-ecs…
Browse files Browse the repository at this point in the history
…-placement-strategy

Eventbridge / Cloudwatch  - ECS target placement strategy
  • Loading branch information
ewbankkit authored Apr 20, 2023
2 parents e51dfe0 + b3d7e06 commit 8226457
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/28384.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_cloudwatch_event_target: Add `ecs_target.ordered_placement_strategy` argument
```
74 changes: 74 additions & 0 deletions internal/service/events/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,25 @@ func ResourceTarget() *schema.Resource {
},
},
},
"ordered_placement_strategy": {
Type: schema.TypeList,
Optional: true,
MaxItems: 5,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"field": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringLenBetween(0, 255),
},
"type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice(eventbridge.PlacementStrategyType_Values(), false),
},
},
},
},
"placement_constraint": {
Type: schema.TypeSet,
Optional: true,
Expand Down Expand Up @@ -779,6 +798,10 @@ func expandTargetECSParameters(ctx context.Context, config []interface{}) *event
ecsParameters.PlacementConstraints = expandTargetPlacementConstraints(v.List())
}

if v, ok := param["ordered_placement_strategy"]; ok {
ecsParameters.PlacementStrategy = expandTargetPlacementStrategies(v.([]interface{}))
}

if v, ok := param["propagate_tags"].(string); ok {
ecsParameters.PropagateTags = aws.String(v)
}
Expand Down Expand Up @@ -972,6 +995,10 @@ func flattenTargetECSParameters(ctx context.Context, ecsParameters *eventbridge.
config["placement_constraint"] = flattenTargetPlacementConstraints(ecsParameters.PlacementConstraints)
}

if ecsParameters.PlacementStrategy != nil {
config["ordered_placement_strategy"] = flattenTargetPlacementStrategies(ecsParameters.PlacementStrategy)
}

if ecsParameters.CapacityProviderStrategy != nil {
config["capacity_provider_strategy"] = flattenTargetCapacityProviderStrategy(ecsParameters.CapacityProviderStrategy)
}
Expand Down Expand Up @@ -1131,6 +1158,36 @@ func expandTargetPlacementConstraints(tfList []interface{}) []*eventbridge.Place
return result
}

func expandTargetPlacementStrategies(tfList []interface{}) []*eventbridge.PlacementStrategy {
if len(tfList) == 0 {
return nil
}

var result []*eventbridge.PlacementStrategy

for _, tfMapRaw := range tfList {
if tfMapRaw == nil {
continue
}

tfMap := tfMapRaw.(map[string]interface{})

apiObject := &eventbridge.PlacementStrategy{}

if v, ok := tfMap["field"].(string); ok && v != "" {
apiObject.Field = aws.String(v)
}

if v, ok := tfMap["type"].(string); ok && v != "" {
apiObject.Type = aws.String(v)
}

result = append(result, apiObject)
}

return result
}

func expandTargetCapacityProviderStrategy(tfList []interface{}) []*eventbridge.CapacityProviderStrategyItem {
if len(tfList) == 0 {
return nil
Expand Down Expand Up @@ -1182,6 +1239,23 @@ func flattenTargetPlacementConstraints(pcs []*eventbridge.PlacementConstraint) [
return results
}

func flattenTargetPlacementStrategies(pcs []*eventbridge.PlacementStrategy) []map[string]interface{} {
if len(pcs) == 0 {
return nil
}
results := make([]map[string]interface{}, 0)
for _, pc := range pcs {
c := make(map[string]interface{})
c["type"] = aws.StringValue(pc.Type)
if pc.Field != nil {
c["field"] = aws.StringValue(pc.Field)
}

results = append(results, c)
}
return results
}

func flattenTargetCapacityProviderStrategy(cps []*eventbridge.CapacityProviderStrategyItem) []map[string]interface{} {
if cps == nil {
return nil
Expand Down
120 changes: 120 additions & 0 deletions internal/service/events/target_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ func TestAccEventsTarget_ecs(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "ecs_target.0.launch_type", "FARGATE"),
resource.TestCheckResourceAttr(resourceName, "ecs_target.0.network_configuration.#", "1"),
resource.TestCheckResourceAttr(resourceName, "ecs_target.0.network_configuration.0.subnets.#", "1"),
resource.TestCheckResourceAttr(resourceName, "ecs_target.0.ordered_placement_strategy.#", "0"),
),
},
{
Expand Down Expand Up @@ -693,6 +694,41 @@ func TestAccEventsTarget_ecsCapacityProvider(t *testing.T) {
})
}

func TestAccEventsTarget_ecsPlacementStrategy(t *testing.T) {
ctx := acctest.Context(t)
var v eventbridge.Target
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_cloudwatch_event_target.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, eventbridge.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckTargetDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccTargetConfig_ecsPlacementStrategy(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckTargetExists(ctx, resourceName, &v),
resource.TestCheckResourceAttr(resourceName, "ecs_target.#", "1"),
resource.TestCheckResourceAttr(resourceName, "ecs_target.0.task_count", "1"),
resource.TestCheckResourceAttr(resourceName, "ecs_target.0.ordered_placement_strategy.#", "2"),
resource.TestCheckResourceAttr(resourceName, "ecs_target.0.ordered_placement_strategy.0.type", "spread"),
resource.TestCheckResourceAttr(resourceName, "ecs_target.0.ordered_placement_strategy.0.field", "attribute:ecs.availability-zone"),
resource.TestCheckResourceAttr(resourceName, "ecs_target.0.ordered_placement_strategy.1.type", "spread"),
resource.TestCheckResourceAttr(resourceName, "ecs_target.0.ordered_placement_strategy.1.field", "instanceId"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateIdFunc: testAccTargetImportStateIdFunc(resourceName),
ImportStateVerify: true,
},
},
})
}

func TestAccEventsTarget_batch(t *testing.T) {
ctx := acctest.Context(t)
resourceName := "aws_cloudwatch_event_target.test"
Expand Down Expand Up @@ -1792,6 +1828,90 @@ resource "aws_ecs_capacity_provider" "test" {
`, rName))
}

func testAccTargetConfig_ecsPlacementStrategy(rName string) string {
return acctest.ConfigCompose(
acctest.ConfigLatestAmazonLinuxHVMEBSAMI(),
testAccTargetConfig_ecsBase(rName),
fmt.Sprintf(`
resource "aws_cloudwatch_event_target" "test" {
arn = aws_ecs_cluster.test.id
rule = aws_cloudwatch_event_rule.test.id
role_arn = aws_iam_role.test.arn
target_id = %[1]q
ecs_target {
task_definition_arn = aws_ecs_task_definition.task.arn
launch_type = "EC2"
ordered_placement_strategy {
type = "spread"
field = "attribute:ecs.availability-zone"
}
ordered_placement_strategy {
type = "spread"
field = "instanceId"
}
}
}
resource "aws_launch_template" "test" {
name = %[1]q
image_id = data.aws_ami.amzn-ami-minimal-hvm-ebs.id
instance_type = "t2.micro"
}
resource "aws_autoscaling_group" "test" {
name = %[1]q
desired_capacity = 0
max_size = 0
min_size = 0
vpc_zone_identifier = [aws_subnet.test[0].id]
launch_template {
id = aws_launch_template.test.id
version = "$Latest"
}
tag {
key = "AmazonECSManaged"
value = ""
propagate_at_launch = true
}
}
resource "aws_ecs_capacity_provider" "test" {
name = %[1]q
auto_scaling_group_provider {
auto_scaling_group_arn = aws_autoscaling_group.test.arn
managed_termination_protection = "DISABLED"
managed_scaling {
maximum_scaling_step_size = 1
minimum_scaling_step_size = 1
status = "ENABLED"
target_capacity = 1
}
}
}
resource "aws_ecs_cluster_capacity_providers" "test" {
cluster_name = aws_ecs_cluster.test.name
capacity_providers = ["%[1]s"]
default_capacity_provider_strategy {
capacity_provider = %[1]q
base = 1
weight = 100
}
}
`, rName))
}

func testAccTargetConfig_batch(rName string) string {
return fmt.Sprintf(`
resource "aws_cloudwatch_event_rule" "test" {
Expand Down
6 changes: 6 additions & 0 deletions website/docs/r/cloudwatch_event_target.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ The following arguments are optional:
* `group` - (Optional) Specifies an ECS task group for the task. The maximum length is 255 characters.
* `launch_type` - (Optional) Specifies the launch type on which your task is running. The launch type that you specify here must match one of the launch type (compatibilities) of the target task. Valid values include: `EC2`, `EXTERNAL`, or `FARGATE`.
* `network_configuration` - (Optional) Use this if the ECS task uses the awsvpc network mode. This specifies the VPC subnets and security groups associated with the task, and whether a public IP address is to be used. Required if `launch_type` is `FARGATE` because the awsvpc mode is required for Fargate tasks.
* `ordered_placement_strategy` - (Optional) An array of placement strategy objects to use for the task. You can specify a maximum of five strategy rules per task.
* `placement_constraint` - (Optional) An array of placement constraint objects to use for the task. You can specify up to 10 constraints per task (including constraints in the task definition and those specified at runtime). See Below.
* `platform_version` - (Optional) Specifies the platform version for the task. Specify only the numeric portion of the platform version, such as `1.1.0`. This is used only if LaunchType is FARGATE. For more information about valid platform versions, see [AWS Fargate Platform Versions](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/platform_versions.html).
* `propagate_tags` - (Optional) Specifies whether to propagate the tags from the task definition to the task. If no value is specified, the tags are not propagated. Tags can only be propagated to the task during task creation. The only valid value is: `TASK_DEFINITION`.
Expand Down Expand Up @@ -536,6 +537,11 @@ The following arguments are optional:

For more information, see [Task Networking](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-networking.html)

### ordered_placement_strategy

* `type` - (Required) Type of placement strategy. The only valid values at this time are `binpack`, `random` and `spread`.
* `field` - (Optional) The field to apply the placement strategy against. For the `spread` placement strategy, valid values are `instanceId` (or `host`, which has the same effect), or any platform or custom attribute that is applied to a container instance, such as `attribute:ecs.availability-zone`. For the `binpack` placement strategy, valid values are `cpu` and `memory`. For the `random` placement strategy, this field is not used. For more information, see [Amazon ECS task placement strategies](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-placement-strategies.html).

### placement_constraint

* `type` - (Required) Type of constraint. The only valid values at this time are `memberOf` and `distinctInstance`.
Expand Down

0 comments on commit 8226457

Please sign in to comment.