Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DeploymentAlarms to ECS Service #28521

Merged
merged 13 commits into from
Jan 4, 2023
86 changes: 83 additions & 3 deletions internal/service/ecs/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,31 @@ func ResourceService() *schema.Resource {
},
},
},
"alarms": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
DiffSuppressFunc: verify.SuppressMissingOptionalConfigurationBlock,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"alarm_names": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"enable": {
Type: schema.TypeBool,
Required: true,
},
"rollback": {
Type: schema.TypeBool,
Required: true,
},
},
},
},
"deployment_controller": {
Type: schema.TypeList,
Optional: true,
Expand Down Expand Up @@ -464,6 +489,8 @@ func resourceServiceCreate(d *schema.ResourceData, meta interface{}) error {
input.TaskDefinition = aws.String(v.(string))
}

input.DeploymentConfiguration = &ecs.DeploymentConfiguration{}
bschaatsbergen marked this conversation as resolved.
Show resolved Hide resolved

if schedulingStrategy == ecs.SchedulingStrategyDaemon && deploymentMinimumHealthyPercent != 100 {
input.DeploymentConfiguration = &ecs.DeploymentConfiguration{
MinimumHealthyPercent: aws.Int64(int64(deploymentMinimumHealthyPercent)),
Expand All @@ -478,10 +505,13 @@ func resourceServiceCreate(d *schema.ResourceData, meta interface{}) error {
}

if v, ok := d.GetOk("deployment_circuit_breaker"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
input.DeploymentConfiguration = &ecs.DeploymentConfiguration{}
input.DeploymentConfiguration.DeploymentCircuitBreaker = expandDeploymentCircuitBreaker(v.([]interface{})[0].(map[string]interface{}))
}

if v, ok := d.GetOk("alarms"); ok {
input.DeploymentConfiguration.Alarms = expandAlarms(v.([]interface{}))
}

if v, ok := d.GetOk("cluster"); ok {
input.Cluster = aws.String(v.(string))
}
Expand Down Expand Up @@ -707,6 +737,10 @@ func resourceServiceRead(d *schema.ResourceData, meta interface{}) error {
d.Set("deployment_maximum_percent", service.DeploymentConfiguration.MaximumPercent)
d.Set("deployment_minimum_healthy_percent", service.DeploymentConfiguration.MinimumHealthyPercent)

if err := d.Set("alarms", flattenAlarms(service.DeploymentConfiguration.Alarms)); err != nil {
return fmt.Errorf("error setting alarms: %w", err)
}

if service.DeploymentConfiguration.DeploymentCircuitBreaker != nil {
if err := d.Set("deployment_circuit_breaker", []interface{}{flattenDeploymentCircuitBreaker(service.DeploymentConfiguration.DeploymentCircuitBreaker)}); err != nil {
return fmt.Errorf("error setting deployment_circuit_break: %w", err)
Expand Down Expand Up @@ -774,13 +808,14 @@ func resourceServiceUpdate(d *schema.ResourceData, meta interface{}) error {

schedulingStrategy := d.Get("scheduling_strategy").(string)

if schedulingStrategy == ecs.SchedulingStrategyDaemon {
switch schedulingStrategy {
case ecs.SchedulingStrategyDaemon:
if d.HasChange("deployment_minimum_healthy_percent") {
input.DeploymentConfiguration = &ecs.DeploymentConfiguration{
MinimumHealthyPercent: aws.Int64(int64(d.Get("deployment_minimum_healthy_percent").(int))),
}
}
} else if schedulingStrategy == ecs.SchedulingStrategyReplica {
case ecs.SchedulingStrategyReplica:
bschaatsbergen marked this conversation as resolved.
Show resolved Hide resolved
if d.HasChange("desired_count") {
input.DesiredCount = aws.Int64(int64(d.Get("desired_count").(int)))
}
Expand Down Expand Up @@ -838,6 +873,10 @@ func resourceServiceUpdate(d *schema.ResourceData, meta interface{}) error {
}
}

if d.HasChange("alarms") {
input.DeploymentConfiguration.Alarms = expandAlarms(d.Get("alarms").([]interface{}))
}

if d.HasChange("platform_version") {
input.PlatformVersion = aws.String(d.Get("platform_version").(string))
}
Expand Down Expand Up @@ -1082,6 +1121,47 @@ func capacityProviderStrategyForceNew(d *schema.ResourceDiff) error {
return nil
}

func expandAlarms(tfList []interface{}) *ecs.DeploymentAlarms {
if len(tfList) == 0 || tfList[0] == nil {
return nil
}

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

apiObject := &ecs.DeploymentAlarms{}

if v, ok := tfMap["enable"].(bool); ok {
apiObject.Enable = aws.Bool(v)
}

if v, ok := tfMap["enable"].(bool); ok {
apiObject.Rollback = aws.Bool(v)
}

if v, ok := tfMap["alarm_names"].(*schema.Set); ok && v.Len() > 0 {
apiObject.AlarmNames = flex.ExpandStringSet(v)
}

return apiObject
}

func flattenAlarms(apiObject *ecs.DeploymentAlarms) []interface{} {
if apiObject == nil {
return []interface{}{}
}

tfMap := map[string]interface{}{}

if v := apiObject.AlarmNames; v != nil {
tfMap["alarm_names"] = aws.StringValueSlice(v)
}

tfMap["enable"] = aws.BoolValue(apiObject.Enable)
tfMap["rollback"] = aws.BoolValue(apiObject.Rollback)

return []interface{}{tfMap}
}

func expandDeploymentController(l []interface{}) *ecs.DeploymentController {
if len(l) == 0 || l[0] == nil {
return nil
Expand Down
73 changes: 73 additions & 0 deletions internal/service/ecs/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,28 @@ func TestAccECSService_DeploymentControllerType_external(t *testing.T) {
})
}

func TestAccECSService_Alarms(t *testing.T) {
var service ecs.Service
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_ecs_service.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ErrorCheck: acctest.ErrorCheck(t, ecs.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckServiceDestroy,
Steps: []resource.TestStep{
{
Config: testAccServiceConfig_alarms(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckServiceExists(resourceName, &service),
resource.TestCheckResourceAttr(resourceName, "alarms.#", "1"),
),
},
},
})
}

func TestAccECSService_DeploymentValues_basic(t *testing.T) {
var service ecs.Service
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
Expand Down Expand Up @@ -2559,6 +2581,57 @@ resource "aws_ecs_service" "test" {
`, rName)
}

func testAccServiceConfig_alarms(rName string) string {
return fmt.Sprintf(`
resource "aws_ecs_cluster" "default" {
name = %[1]q
}

resource "aws_ecs_task_definition" "test" {
family = %[1]q

container_definitions = <<DEFINITION
[
{
"cpu": 128,
"essential": true,
"image": "mongo:latest",
"memory": 128,
"name": "mongodb"
}
]
DEFINITION
}

resource "aws_ecs_service" "test" {
name = %[1]q
cluster = aws_ecs_cluster.default.id
task_definition = aws_ecs_task_definition.test.arn
desired_count = 1

alarms {
enable = true
rollback = true
alarm_names = [
aws_cloudwatch_metric_alarm.test.alarm_name
]
}
}

resource "aws_cloudwatch_metric_alarm" "test" {
alarm_name = %[1]q
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = "2"
metric_name = "CPUReservation"
namespace = "AWS/ECS"
period = "120"
statistic = "Average"
threshold = "80"
insufficient_data_actions = []
}
`, rName)
}

func testAccServiceConfig_deploymentValues(rName string) string {
return fmt.Sprintf(`
resource "aws_ecs_cluster" "default" {
Expand Down
26 changes: 26 additions & 0 deletions website/docs/r/ecs_service.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,23 @@ resource "aws_ecs_service" "bar" {
}
```

### CloudWatch Deployment Alarms

```terraform
resource "aws_ecs_service" "example" {
name = "example"
cluster = aws_ecs_cluster.example.id

alarms {
enable = true
rollback = true
alarm_names = [
aws_cloudwatch_metric_alarm.example.alarm_name
]
}
}
```

### External Deployment Controller

```terraform
Expand Down Expand Up @@ -109,6 +126,7 @@ The following arguments are required:

The following arguments are optional:

* `alarms` - (Optional) Information about the CloudWatch alarms. [See below](#alarms).
* `capacity_provider_strategy` - (Optional) Capacity provider strategies to use for the service. Can be one or more. These can be updated without destroying and recreating the service only if `force_new_deployment = true` and not changing from 0 `capacity_provider_strategy` blocks to greater than 0, or vice versa. See below.
* `cluster` - (Optional) ARN of an ECS cluster.
* `deployment_circuit_breaker` - (Optional) Configuration block for deployment circuit breaker. See below.
Expand Down Expand Up @@ -136,6 +154,14 @@ The following arguments are optional:
* `triggers` - (Optional) Map of arbitrary keys and values that, when changed, will trigger an in-place update (redeployment). Useful with `timestamp()`. See example above.
* `wait_for_steady_state` - (Optional) If `true`, Terraform will wait for the service to reach a steady state (like [`aws ecs wait services-stable`](https://docs.aws.amazon.com/cli/latest/reference/ecs/wait/services-stable.html)) before continuing. Default `false`.

### alarms

The `alarms` configuration block supports the following:

* `alarms_names` - (Required) One or more CloudWatch alarm names. Use a "," to separate the alarms.
bschaatsbergen marked this conversation as resolved.
Show resolved Hide resolved
* `enable` - (Required) Determines whether to use the CloudWatch alarm option in the service deployment process.
* `rollback` - (Required) Determines whether to configure Amazon ECS to roll back the service if a service deployment fails. If rollback is used, when a service deployment fails, the service is rolled back to the last deployment that completed successfully.

### capacity_provider_strategy

The `capacity_provider_strategy` configuration block supports the following:
Expand Down