Skip to content

Commit

Permalink
Merge pull request #25840 from obataku/f-aws_ecs_service-update-triggers
Browse files Browse the repository at this point in the history
r/aws_ecs_service: add triggers attribute to force update in-place
  • Loading branch information
YakDriver authored Nov 17, 2022
2 parents 0d7737b + c86dbcc commit e8e7053
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/25840.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_ecs_service: Add `triggers` argument to enable in-place updates (redeployments) on each apply, when used with `force_new_deployment = true`
```
34 changes: 34 additions & 0 deletions internal/service/ecs/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,14 @@ func ResourceService() *schema.Resource {
Type: schema.TypeString,
Optional: true,
},
// modeled after null_resource & aws_api_gateway_deployment
// only for _updates in-place_ rather than replacements
"triggers": {
Type: schema.TypeMap,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"wait_for_steady_state": {
Type: schema.TypeBool,
Optional: true,
Expand All @@ -350,10 +358,34 @@ func ResourceService() *schema.Resource {
CustomizeDiff: customdiff.Sequence(
verify.SetTagsDiff,
capacityProviderStrategyCustomizeDiff,
triggersCustomizeDiff,
),
}
}

func triggersCustomizeDiff(_ context.Context, d *schema.ResourceDiff, meta interface{}) error {
// clears diff to avoid extraneous diffs but lets it pass for triggering update
fnd := false
if v, ok := d.GetOk("force_new_deployment"); ok {
fnd = v.(bool)
}

if d.HasChange("triggers") && !fnd {
return d.Clear("triggers")
}

if d.HasChange("triggers") && fnd {
o, n := d.GetChange("triggers")
if len(o.(map[string]interface{})) > 0 && len(n.(map[string]interface{})) == 0 {
return d.Clear("triggers")
}

return nil
}

return nil
}

func capacityProviderStrategyCustomizeDiff(_ context.Context, d *schema.ResourceDiff, meta interface{}) error {
// to be backward compatible, should ForceNew almost always (previous behavior), unless:
// force_new_deployment is true and
Expand Down Expand Up @@ -642,6 +674,8 @@ func resourceServiceRead(d *schema.ResourceData, meta interface{}) error {
d.Set("platform_version", service.PlatformVersion)
d.Set("enable_execute_command", service.EnableExecuteCommand)

d.Set("triggers", d.Get("triggers"))

// Save cluster in the same format
if strings.HasPrefix(d.Get("cluster").(string), "arn:"+meta.(*conns.AWSClient).Partition+":ecs:") {
d.Set("cluster", service.ClusterArn)
Expand Down
77 changes: 77 additions & 0 deletions internal/service/ecs/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,42 @@ func TestAccECSService_forceNewDeployment(t *testing.T) {
})
}

func TestAccECSService_forceNewDeploymentTriggers(t *testing.T) {
var service1, service2 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_forceNewDeployment(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckServiceExists(resourceName, &service1),
resource.TestCheckResourceAttr(resourceName, "ordered_placement_strategy.#", "1"),
resource.TestCheckResourceAttr(resourceName, "ordered_placement_strategy.0.type", "binpack"),
resource.TestCheckResourceAttr(resourceName, "ordered_placement_strategy.0.field", "memory"),
),
},
{
Config: testAccServiceConfig_forceNewDeploymentTriggers(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckServiceExists(resourceName, &service2),
testAccCheckServiceNotRecreated(&service1, &service2),
resource.TestCheckResourceAttr(resourceName, "force_new_deployment", "true"),
resource.TestCheckResourceAttrSet(resourceName, "triggers.update"),
resource.TestCheckResourceAttr(resourceName, "ordered_placement_strategy.#", "1"),
resource.TestCheckResourceAttr(resourceName, "ordered_placement_strategy.0.type", "binpack"),
resource.TestCheckResourceAttr(resourceName, "ordered_placement_strategy.0.field", "memory"),
),
},
},
})
}

func TestAccECSService_PlacementStrategy_basic(t *testing.T) {
var service1, service2, service3, service4 ecs.Service
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
Expand Down Expand Up @@ -1941,6 +1977,47 @@ resource "aws_ecs_service" "test" {
`, rName)
}

func testAccServiceConfig_forceNewDeploymentTriggers(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" {
cluster = aws_ecs_cluster.default.id
desired_count = 1
force_new_deployment = true
name = %[1]q
task_definition = aws_ecs_task_definition.test.arn
ordered_placement_strategy {
type = "binpack"
field = "memory"
}
triggers = {
update = timestamp()
}
}
`, rName)
}

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

### Redeploy Service On Every Apply

The key used with `triggers` is arbitrary.

```terraform
resource "aws_ecs_service" "example" {
# ... other configurations ...
force_new_deployment = true
triggers = {
redeployment = timestamp()
}
}
```

## Argument Reference

The following arguments are required:
Expand Down Expand Up @@ -116,6 +132,7 @@ The following arguments are optional:
* `service_registries` - (Optional) Service discovery registries for the service. The maximum number of `service_registries` blocks is `1`. See below.
* `tags` - (Optional) Key-value map of resource tags. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level.
* `task_definition` - (Optional) Family and revision (`family:revision`) or full ARN of the task definition that you want to run in your service. Required unless using the `EXTERNAL` deployment controller. If a revision is not specified, the latest `ACTIVE` revision is used.
* `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`.

### capacity_provider_strategy
Expand Down

0 comments on commit e8e7053

Please sign in to comment.