From a34fac97f124e21ef0b8aca939d76fed55b8ee33 Mon Sep 17 00:00:00 2001 From: obataku <19821199+obataku@users.noreply.github.com> Date: Fri, 15 Jul 2022 15:34:35 -0400 Subject: [PATCH 1/6] r/aws_ecs_service: add triggers attribute to force update in-place closes #13931 #13528 --- .changelog/25840.txt | 3 +++ internal/service/ecs/service.go | 7 +++++++ website/docs/r/ecs_service.html.markdown | 1 + 3 files changed, 11 insertions(+) create mode 100644 .changelog/25840.txt diff --git a/.changelog/25840.txt b/.changelog/25840.txt new file mode 100644 index 00000000000..95f3632c60f --- /dev/null +++ b/.changelog/25840.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_ecs_service: Add triggers attribute to force update in-place via `UpdateService`, e.g. in conjunction with `force_new_deployment = true` to force a new deployment. +``` diff --git a/internal/service/ecs/service.go b/internal/service/ecs/service.go index 4ebe09b4b75..aef385f18d2 100644 --- a/internal/service/ecs/service.go +++ b/internal/service/ecs/service.go @@ -161,6 +161,13 @@ func ResourceService() *schema.Resource { Type: schema.TypeBool, Optional: true, }, + // modeled after null_resource & aws_api_gateway_deployment + // only for _updates in-place_ rather than replacements + "triggers": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, "health_check_grace_period_seconds": { Type: schema.TypeInt, Optional: true, diff --git a/website/docs/r/ecs_service.html.markdown b/website/docs/r/ecs_service.html.markdown index d65f6b5e166..2e79f97d06c 100644 --- a/website/docs/r/ecs_service.html.markdown +++ b/website/docs/r/ecs_service.html.markdown @@ -103,6 +103,7 @@ The following arguments are optional: * `enable_ecs_managed_tags` - (Optional) Specifies whether to enable Amazon ECS managed tags for the tasks within the service. * `enable_execute_command` - (Optional) Specifies whether to enable Amazon ECS Exec for the tasks within the service. * `force_new_deployment` - (Optional) Enable to force a new task deployment of the service. This can be used to update tasks to use a newer Docker image with same image/tag combination (e.g., `myimage:latest`), roll Fargate tasks onto a newer platform version, or immediately deploy `ordered_placement_strategy` and `placement_constraints` updates. +* `triggers` - (Optional) Map of arbitrary keys and values that, when changed, will trigger an update in-place via [`UpdateService`](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_UpdateService.html). * `health_check_grace_period_seconds` - (Optional) Seconds to ignore failing load balancer health checks on newly instantiated tasks to prevent premature shutdown, up to 2147483647. Only valid for services configured to use load balancers. * `iam_role` - (Optional) ARN of the IAM role that allows Amazon ECS to make calls to your load balancer on your behalf. This parameter is required if you are using a load balancer with your service, but only if your task definition does not use the `awsvpc` network mode. If using `awsvpc` network mode, do not specify this role. If your account has already created the Amazon ECS service-linked role, that role is used by default for your service unless you specify a role here. * `launch_type` - (Optional) Launch type on which to run your service. The valid values are `EC2`, `FARGATE`, and `EXTERNAL`. Defaults to `EC2`. From 03e1aa40d8a37697f7124debb12adbe63cde4da7 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 17 Nov 2022 14:34:35 -0500 Subject: [PATCH 2/6] ecs/service: Clean up triggers diffs --- internal/service/ecs/service.go | 43 +++++++++++++--- internal/service/ecs/service_test.go | 77 ++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 7 deletions(-) diff --git a/internal/service/ecs/service.go b/internal/service/ecs/service.go index aef385f18d2..de77e1a5c9e 100644 --- a/internal/service/ecs/service.go +++ b/internal/service/ecs/service.go @@ -161,13 +161,6 @@ func ResourceService() *schema.Resource { Type: schema.TypeBool, Optional: true, }, - // modeled after null_resource & aws_api_gateway_deployment - // only for _updates in-place_ rather than replacements - "triggers": { - Type: schema.TypeMap, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, "health_check_grace_period_seconds": { Type: schema.TypeInt, Optional: true, @@ -347,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, @@ -357,10 +358,36 @@ 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 { + d.Clear("triggers") + + return nil + } + + if d.HasChange("triggers") && fnd { + o, n := d.GetChange("triggers") + if len(o.(map[string]interface{})) > 0 && len(n.(map[string]interface{})) == 0 { + 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 @@ -649,6 +676,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) diff --git a/internal/service/ecs/service_test.go b/internal/service/ecs/service_test.go index f6f9bc33ddd..820cb8d8066 100644 --- a/internal/service/ecs/service_test.go +++ b/internal/service/ecs/service_test.go @@ -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) @@ -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 = < Date: Thu, 17 Nov 2022 14:34:56 -0500 Subject: [PATCH 3/6] docs/ecs/service: Update docs --- website/docs/r/ecs_service.html.markdown | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/website/docs/r/ecs_service.html.markdown b/website/docs/r/ecs_service.html.markdown index 2e79f97d06c..dfafd462e47 100644 --- a/website/docs/r/ecs_service.html.markdown +++ b/website/docs/r/ecs_service.html.markdown @@ -85,6 +85,22 @@ resource "aws_ecs_service" "example" { } ``` +### Update In-place 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: @@ -103,7 +119,6 @@ The following arguments are optional: * `enable_ecs_managed_tags` - (Optional) Specifies whether to enable Amazon ECS managed tags for the tasks within the service. * `enable_execute_command` - (Optional) Specifies whether to enable Amazon ECS Exec for the tasks within the service. * `force_new_deployment` - (Optional) Enable to force a new task deployment of the service. This can be used to update tasks to use a newer Docker image with same image/tag combination (e.g., `myimage:latest`), roll Fargate tasks onto a newer platform version, or immediately deploy `ordered_placement_strategy` and `placement_constraints` updates. -* `triggers` - (Optional) Map of arbitrary keys and values that, when changed, will trigger an update in-place via [`UpdateService`](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_UpdateService.html). * `health_check_grace_period_seconds` - (Optional) Seconds to ignore failing load balancer health checks on newly instantiated tasks to prevent premature shutdown, up to 2147483647. Only valid for services configured to use load balancers. * `iam_role` - (Optional) ARN of the IAM role that allows Amazon ECS to make calls to your load balancer on your behalf. This parameter is required if you are using a load balancer with your service, but only if your task definition does not use the `awsvpc` network mode. If using `awsvpc` network mode, do not specify this role. If your account has already created the Amazon ECS service-linked role, that role is used by default for your service unless you specify a role here. * `launch_type` - (Optional) Launch type on which to run your service. The valid values are `EC2`, `FARGATE`, and `EXTERNAL`. Defaults to `EC2`. @@ -117,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. 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 From 4f3003a97366270a4d20b1ada16d0b524c19799b Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 17 Nov 2022 14:37:57 -0500 Subject: [PATCH 4/6] Update changelog --- .changelog/25840.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/25840.txt b/.changelog/25840.txt index 95f3632c60f..453d2b27f07 100644 --- a/.changelog/25840.txt +++ b/.changelog/25840.txt @@ -1,3 +1,3 @@ ```release-note:enhancement -resource/aws_ecs_service: Add triggers attribute to force update in-place via `UpdateService`, e.g. in conjunction with `force_new_deployment = true` to force a new deployment. +resource/aws_ecs_service: Add `triggers` argument to enable in-place updates (redeployments) on each apply, when used with `force_new_deployment = true` ``` From c65f5fa6f0459a0a3eae34c607184ec253926218 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 17 Nov 2022 14:47:48 -0500 Subject: [PATCH 5/6] docs/ecs/service: Update --- website/docs/r/ecs_service.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/r/ecs_service.html.markdown b/website/docs/r/ecs_service.html.markdown index dfafd462e47..ca649b10a90 100644 --- a/website/docs/r/ecs_service.html.markdown +++ b/website/docs/r/ecs_service.html.markdown @@ -85,7 +85,7 @@ resource "aws_ecs_service" "example" { } ``` -### Update In-place On Every Apply +### Redeploy Service On Every Apply The key used with `triggers` is arbitrary. @@ -132,7 +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. See example above. +* `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 From c86dbcc115645f3184b7abb98a8b45b532ca4789 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 17 Nov 2022 15:04:09 -0500 Subject: [PATCH 6/6] ecs/service: Check return value --- internal/service/ecs/service.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/service/ecs/service.go b/internal/service/ecs/service.go index de77e1a5c9e..6bf22595cee 100644 --- a/internal/service/ecs/service.go +++ b/internal/service/ecs/service.go @@ -371,15 +371,13 @@ func triggersCustomizeDiff(_ context.Context, d *schema.ResourceDiff, meta inter } if d.HasChange("triggers") && !fnd { - d.Clear("triggers") - - return nil + 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 { - d.Clear("triggers") + return d.Clear("triggers") } return nil